Merge remote-tracking branch 'origin/explore-page-trending-and-starterpacks' into hailey/add-topic-screen

This commit is contained in:
Hailey
2024-12-17 19:01:48 -08:00
80 changed files with 1642 additions and 509 deletions
+16
View File
@@ -105,3 +105,19 @@ jest.mock('expo-modules-core', () => ({
return () => null 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
View File
@@ -120,7 +120,7 @@
"emoji-mart": "^5.5.2", "emoji-mart": "^5.5.2",
"emoji-regex": "^10.4.0", "emoji-regex": "^10.4.0",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"expo": "~52.0.17", "expo": "~52.0.19",
"expo-application": "^6.0.1", "expo-application": "^6.0.1",
"expo-blur": "^14.0.1", "expo-blur": "^14.0.1",
"expo-build-properties": "^0.13.1", "expo-build-properties": "^0.13.1",
@@ -128,7 +128,7 @@
"expo-clipboard": "^7.0.0", "expo-clipboard": "^7.0.0",
"expo-dev-client": "^5.0.4", "expo-dev-client": "^5.0.4",
"expo-device": "~7.0.1", "expo-device": "~7.0.1",
"expo-file-system": "^18.0.4", "expo-file-system": "^18.0.6",
"expo-font": "~13.0.1", "expo-font": "~13.0.1",
"expo-haptics": "^14.0.0", "expo-haptics": "^14.0.0",
"expo-image": "~2.0.3", "expo-image": "~2.0.3",
@@ -141,7 +141,7 @@
"expo-navigation-bar": "~4.0.4", "expo-navigation-bar": "~4.0.4",
"expo-notifications": "~0.29.11", "expo-notifications": "~0.29.11",
"expo-sharing": "^13.0.0", "expo-sharing": "^13.0.0",
"expo-splash-screen": "~0.29.16", "expo-splash-screen": "~0.29.18",
"expo-status-bar": "~2.0.0", "expo-status-bar": "~2.0.0",
"expo-system-ui": "^4.0.4", "expo-system-ui": "^4.0.4",
"expo-task-manager": "~12.0.3", "expo-task-manager": "~12.0.3",
@@ -213,7 +213,6 @@
"@babel/runtime": "^7.26.0", "@babel/runtime": "^7.26.0",
"@did-plc/server": "^0.0.1", "@did-plc/server": "^0.0.1",
"@expo/config-plugins": "9.0.10", "@expo/config-plugins": "9.0.10",
"@expo/prebuild-config": "8.0.22",
"@lingui/cli": "^4.14.1", "@lingui/cli": "^4.14.1",
"@lingui/macro": "^4.14.1", "@lingui/macro": "^4.14.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
-35
View File
@@ -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`");
}
+15
View File
@@ -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
+36 -1
View File
@@ -222,7 +222,7 @@ index 40aaf9c..1c60164 100644
{ {
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) { [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 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 --- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
+++ b/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 @@ -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)
+12
View File
@@ -302,6 +302,18 @@ export const atoms = {
border_0: { border_0: {
borderWidth: 0, borderWidth: 0,
}, },
border_t_0: {
borderTopWidth: 0,
},
border_b_0: {
borderBottomWidth: 0,
},
border_l_0: {
borderLeftWidth: 0,
},
border_r_0: {
borderRightWidth: 0,
},
border: { border: {
borderWidth: StyleSheet.hairlineWidth, borderWidth: StyleSheet.hairlineWidth,
}, },
+1 -1
View File
@@ -280,8 +280,8 @@ export function ProfileGrid({
profile={profile} profile={profile}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}
logContext="FeedInterstitial" logContext="FeedInterstitial"
color="secondary_inverted"
shape="round" shape="round"
colorInverted
/> />
</ProfileCard.Header> </ProfileCard.Header>
<ProfileCard.Description profile={profile} numberOfLines={2} /> <ProfileCard.Description profile={profile} numberOfLines={2} />
+6 -1
View File
@@ -285,6 +285,7 @@ export type FollowButtonProps = {
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
logContext: LogEvents['profile:follow']['logContext'] & logContext: LogEvents['profile:follow']['logContext'] &
LogEvents['profile:unfollow']['logContext'] LogEvents['profile:unfollow']['logContext']
colorInverted?: boolean
} & Partial<ButtonProps> } & Partial<ButtonProps>
export function FollowButton(props: FollowButtonProps) { export function FollowButton(props: FollowButtonProps) {
@@ -297,6 +298,8 @@ export function FollowButtonInner({
profile: profileUnshadowed, profile: profileUnshadowed,
moderationOpts, moderationOpts,
logContext, logContext,
onPress: onPressProp,
colorInverted,
...rest ...rest
}: FollowButtonProps) { }: FollowButtonProps) {
const {_} = useLingui() const {_} = useLingui()
@@ -321,6 +324,7 @@ export function FollowButtonInner({
)}`, )}`,
), ),
) )
onPressProp?.(e)
} catch (err: any) { } catch (err: any) {
if (err?.name !== 'AbortError') { if (err?.name !== 'AbortError') {
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark') Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
@@ -341,6 +345,7 @@ export function FollowButtonInner({
)}`, )}`,
), ),
) )
onPressProp?.(e)
} catch (err: any) { } catch (err: any) {
if (err?.name !== 'AbortError') { if (err?.name !== 'AbortError') {
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark') Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
@@ -387,7 +392,7 @@ export function FollowButtonInner({
label={followLabel} label={followLabel}
size="small" size="small"
variant="solid" variant="solid"
color="primary" color={colorInverted ? 'secondary_inverted' : 'primary'}
{...rest} {...rest}
onPress={onPressFollow}> onPress={onPressFollow}>
<ButtonIcon icon={Plus} position={isRound ? undefined : 'left'} /> <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
}
}
+31 -13
View File
@@ -10,12 +10,15 @@ import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {FollowDialog} from './FollowDialog'
import {ProgressGuideTask} from './Task' import {ProgressGuideTask} from './Task'
export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) { export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() 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() const {endProgressGuide} = useProgressGuideControls()
if (guide) { if (guide) {
@@ -41,18 +44,33 @@ export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
<ButtonIcon icon={Times} size="sm" /> <ButtonIcon icon={Times} size="sm" />
</Button> </Button>
</View> </View>
<ProgressGuideTask {guide.guide === 'follow-10' && (
current={guide.numLikes + 1} <>
total={10 + 1} <ProgressGuideTask
title={_(msg`Like 10 posts`)} current={guide.numFollows + 1}
subtitle={_(msg`Teach our algorithm what you like`)} total={10 + 1}
/> title={_(msg`Follow 10 accounts`)}
<ProgressGuideTask subtitle={_(msg`Bluesky is better with friends!`)}
current={guide.numFollows + 1} />
total={7 + 1} <FollowDialog guide={guide} />
title={_(msg`Follow 7 accounts`)} </>
subtitle={_(msg`Bluesky is better with friends!`)} )}
/> {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> </View>
) )
} }
+12 -2
View File
@@ -10,11 +10,13 @@ export function ProgressGuideTask({
total, total,
title, title,
subtitle, subtitle,
tabularNumsTitle,
}: { }: {
current: number current: number
total: number total: number
title: string title: string
subtitle?: string subtitle?: string
tabularNumsTitle?: boolean
}) { }) {
const t = useTheme() const t = useTheme()
@@ -33,8 +35,16 @@ export function ProgressGuideTask({
/> />
)} )}
<View style={[a.flex_col, a.gap_2xs, {marginTop: -2}]}> <View style={[a.flex_col, a.gap_2xs, subtitle && {marginTop: -2}]}>
<Text style={[a.text_sm, a.font_bold, a.leading_tight]}>{title}</Text> <Text
style={[
a.text_sm,
a.font_bold,
a.leading_tight,
tabularNumsTitle && {fontVariant: ['tabular-nums']},
]}>
{title}
</Text>
{subtitle && ( {subtitle && (
<Text <Text
style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_tight]}> style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_tight]}>
@@ -63,6 +63,7 @@ export function SearchablePeopleList({
const {_} = useLingui() const {_} = useLingui()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const control = Dialog.useDialogContext() const control = Dialog.useDialogContext()
const [headerHeight, setHeaderHeight] = useState(0)
const listRef = useRef<ListMethods>(null) const listRef = useRef<ListMethods>(null)
const {currentAccount} = useSession() const {currentAccount} = useSession()
const inputRef = useRef<TextInput>(null) const inputRef = useRef<TextInput>(null)
@@ -237,6 +238,7 @@ export function SearchablePeopleList({
const listHeader = useMemo(() => { const listHeader = useMemo(() => {
return ( return (
<View <View
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}
style={[ style={[
a.relative, a.relative,
web(a.pt_lg), web(a.pt_lg),
@@ -315,6 +317,7 @@ export function SearchablePeopleList({
]} ]}
webInnerContentContainerStyle={a.py_0} webInnerContentContainerStyle={a.py_0}
webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]} webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
scrollIndicatorInsets={{top: headerHeight}}
keyboardDismissMode="on-drag" keyboardDismissMode="on-drag"
/> />
) )
+9 -11
View File
@@ -26,16 +26,16 @@ import {Text} from '#/components/Typography'
export function TrendingInterstitial() { export function TrendingInterstitial() {
const {enabled} = useTrendingConfig() const {enabled} = useTrendingConfig()
const {trendingDiscoverHidden} = useTrendingSettings() const {trendingDisabled} = useTrendingSettings()
return !enabled ? null : trendingDiscoverHidden ? null : <Inner /> return enabled && !trendingDisabled ? <Inner /> : null
} }
export function Inner() { export function Inner() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const gutters = useGutters(['base']) const gutters = useGutters(['wide', 'base'])
const trendingPrompt = Prompt.usePromptControl() const trendingPrompt = Prompt.usePromptControl()
const {setTrendingDiscoverHidden} = useTrendingSettingsApi() const {setTrendingDisabled} = useTrendingSettingsApi()
const {data: trending, error, isLoading} = useTrendingTopics() const {data: trending, error, isLoading} = useTrendingTopics()
const noTopics = !isLoading && !error && !trending?.topics?.length const noTopics = !isLoading && !error && !trending?.topics?.length
@@ -63,7 +63,7 @@ export function Inner() {
</View> </View>
<Button <Button
label={_(msg`Hide trending topics from your feed`)} label={_(msg`Hide trending topics`)}
size="tiny" size="tiny"
variant="outline" variant="outline"
color="secondary" color="secondary"
@@ -73,7 +73,7 @@ export function Inner() {
</Button> </Button>
</View> </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 ? ( {isLoading ? (
Array(TRENDING_TOPICS_COUNT) Array(TRENDING_TOPICS_COUNT)
.fill(0) .fill(0)
@@ -101,12 +101,10 @@ export function Inner() {
<Prompt.Basic <Prompt.Basic
control={trendingPrompt} control={trendingPrompt}
title={_(msg`Hide trending topics in your feed?`)} title={_(msg`Hide trending topics?`)}
description={_( description={_(msg`You can update this later from your settings.`)}
msg`This is a device setting, and will apply to all accounts on this device. You can update this later from your settings.`,
)}
confirmButtonCta={_(msg`Hide`)} confirmButtonCta={_(msg`Hide`)}
onConfirm={() => setTrendingDiscoverHidden(true)} onConfirm={() => setTrendingDisabled(true)}
/> />
</View> </View>
) )
+1
View File
@@ -1,5 +1,6 @@
import {init} from '@bitdrift/react-native' import {init} from '@bitdrift/react-native'
import {Statsig} from 'statsig-react-native-expo' import {Statsig} from 'statsig-react-native-expo'
export {debug, error, info, warn} from '@bitdrift/react-native'
import {initPromise} from './statsig/statsig' import {initPromise} from './statsig/statsig'
+4
View File
@@ -0,0 +1,4 @@
export function debug() {}
export function error() {}
export function info() {}
export function warn() {}
-10
View File
@@ -23,16 +23,6 @@ export const STARTER_PACK_MAX_SIZE = 150
// -prf // -prf
export const JOINED_THIS_WEEK = 2880000 // estimate as of 11/26/24 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` const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new`
export function FEEDBACK_FORM_URL({ export function FEEDBACK_FORM_URL({
email, email,
+2
View File
@@ -162,6 +162,7 @@ export type LogEvents = {
| 'StarterPackProfilesList' | 'StarterPackProfilesList'
| 'FeedInterstitial' | 'FeedInterstitial'
| 'ProfileHeaderSuggestedFollows' | 'ProfileHeaderSuggestedFollows'
| 'PostOnboardingFindFollows'
} }
'profile:unfollow': { 'profile:unfollow': {
logContext: logContext:
@@ -177,6 +178,7 @@ export type LogEvents = {
| 'StarterPackProfilesList' | 'StarterPackProfilesList'
| 'FeedInterstitial' | 'FeedInterstitial'
| 'ProfileHeaderSuggestedFollows' | 'ProfileHeaderSuggestedFollows'
| 'PostOnboardingFindFollows'
} }
'chat:create': { 'chat:create': {
logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog' logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog'
+3 -2
View File
@@ -1,6 +1,7 @@
export type Gate = export type Gate =
// Keep this alphabetic please. // Keep this alphabetic please.
| 'debug_show_feedcontext' // DISABLED DUE TO EME | 'debug_show_feedcontext'
| 'post_feed_lang_window' // DISABLED DUE TO EME | 'debug_subscriptions'
| 'new_postonboarding'
| 'remove_show_latest_button' | 'remove_show_latest_button'
| 'trending_topics_beta' | 'trending_topics_beta'
+23 -3
View File
@@ -5,6 +5,7 @@ import {sha256} from 'js-sha256'
import {Statsig, StatsigProvider} from 'statsig-react-native-expo' import {Statsig, StatsigProvider} from 'statsig-react-native-expo'
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info' import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
import * as bitdrift from '#/lib/bitdrift'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
@@ -97,19 +98,38 @@ export function logEvent<E extends keyof LogEvents>(
rawMetadata: LogEvents[E] & FlatJSONRecord, rawMetadata: LogEvents[E] & FlatJSONRecord,
) { ) {
try { try {
const fullMetadata = { const fullMetadata = toStringRecord(rawMetadata)
...rawMetadata,
} as Record<string, string> // Statsig typings are unnecessarily strict here.
fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)' fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)'
if (Statsig.initializeCalled()) { if (Statsig.initializeCalled()) {
Statsig.logEvent(eventName, null, fullMetadata) 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) { } catch (e) {
// A log should never interrupt the calling code, whatever happens. // A log should never interrupt the calling code, whatever happens.
logger.error('Failed to log an event', {message: e}) 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 // 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. // and it's been difficult to get it to behave in a predictable way.
// Our own cache ensures consistent evaluation within a single session. // Our own cache ensures consistent evaluation within a single session.
+3 -3
View File
@@ -3613,7 +3613,7 @@ msgstr "Escribe la tuya clau"
#~ msgstr "Escribe lo tuyo furnidor d'aloch preferiu" #~ msgstr "Escribe lo tuyo furnidor d'aloch preferiu"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Escribe lo tuyo identificador" msgstr "Escribe lo tuyo identificador"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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í." msgstr "La tuya canal de Seguindo ye vueda! Sigue a mas usuarios pa veyer las suyas publicacions aquí."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Lo tuyo identificador completo será" msgstr "Lo tuyo identificador completo será"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Lo tuyo reporte s'ha ninviau a lo servicio de moderación de Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Lo tuyo identificador" msgstr "Lo tuyo identificador"
+3 -3
View File
@@ -3509,7 +3509,7 @@ msgstr ""
#~ msgstr "" #~ msgstr ""
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "" msgstr ""
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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ó." msgstr "¡El feed siguiente ta baleru! Sigui a más usuarios pa ver qué pasó."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "L'identificador completu va ser" msgstr "L'identificador completu va ser"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "L'informe unvióse al serviciu de moderación de Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "" msgstr ""
+3 -3
View File
@@ -4268,7 +4268,7 @@ msgstr "Introdueix la teva contrasenya"
#~ msgstr "Introdueix el teu proveïdor d'allotjament preferit" #~ msgstr "Introdueix el teu proveïdor d'allotjament preferit"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Introdueix el teu identificador d'usuari" msgstr "Introdueix el teu identificador d'usuari"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." 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 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "El teu identificador complet serà" msgstr "El teu identificador complet serà"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "El teu informe s'enviarà al servei de moderació de Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "El teu identificador d'usuari" msgstr "El teu identificador d'usuari"
+3 -3
View File
@@ -3742,7 +3742,7 @@ msgstr "Gib dein Passwort ein"
#~ msgstr "" #~ msgstr ""
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Gib deinen Handle ein" msgstr "Gib deinen Handle ein"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." msgstr "Dein Following-Feed ist leer! Folge mehr Benutzern, um auf dem Laufenden zu bleiben."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Dein vollständiger Handle lautet" msgstr "Dein vollständiger Handle lautet"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -9707,5 +9707,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "" msgstr ""
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Dein Benutzerhandle" msgstr "Dein Benutzerhandle"
+3 -3
View File
@@ -3509,7 +3509,7 @@ msgstr ""
#~ msgstr "" #~ msgstr ""
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "" msgstr ""
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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 "" msgstr ""
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "" msgstr ""
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "" msgstr ""
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "" msgstr ""
+3 -3
View File
@@ -3509,7 +3509,7 @@ msgstr ""
#~ msgstr "" #~ msgstr ""
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "" msgstr ""
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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 "" msgstr ""
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "" msgstr ""
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "" msgstr ""
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "" msgstr ""
+3 -3
View File
@@ -3896,7 +3896,7 @@ msgstr "Introduce tu contraseña"
#~ msgstr "Introduce tu proveedor de alojamiento preferido" #~ msgstr "Introduce tu proveedor de alojamiento preferido"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Introduce tu nombre de usuario" msgstr "Introduce tu nombre de usuario"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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í." msgstr "¡Tu feed de Siguiendo esta vacío! Sigue a más usuarios para ver sus posts aquí."
#: src/screens/Signup/StepHandle.tsx:125 #: 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á" msgstr "Tu nombre de usuario completo será"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Tu reporte ha sido enviado al servicio de moderación de Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Tu nombre de usuario" msgstr "Tu nombre de usuario"
+3 -3
View File
@@ -3970,7 +3970,7 @@ msgstr "Syötä salasanasi"
#~ msgstr "Syötä haluamasi palveluntarjoaja" #~ msgstr "Syötä haluamasi palveluntarjoaja"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Syötä käyttäjätunnuksesi" msgstr "Syötä käyttäjätunnuksesi"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." msgstr "Seuraamiesi syöte on tyhjä! Seuraa lisää käyttäjiä nähdäksesi, mitä tapahtuu."
#: src/screens/Signup/StepHandle.tsx:125 #: 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" msgstr "Käyttäjätunnuksesi tulee olemaan"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -9671,5 +9671,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "" msgstr ""
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Käyttäjätunnuksesi" msgstr "Käyttäjätunnuksesi"
+3 -3
View File
@@ -3501,7 +3501,7 @@ msgstr "Entrez votre mot de passe"
#~ msgstr "Entrez votre hébergeur préféré" #~ msgstr "Entrez votre hébergeur préféré"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Entrez votre pseudo" msgstr "Entrez votre pseudo"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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 dactu des comptes suivis est vide ! Suivez plus de comptes pour voir ce qui se passe." msgstr "Votre fil dactu des comptes suivis est vide ! Suivez plus de comptes pour voir ce qui se passe."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Votre nom complet sera" msgstr "Votre nom complet sera"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Votre rapport sera envoyé au Service de Modération de Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Votre pseudo" msgstr "Votre pseudo"
+3 -3
View File
@@ -3508,7 +3508,7 @@ msgstr "Cuir isteach do phasfhocal"
#~ msgstr "Cuir isteach an soláthraí óstála is fearr leat" #~ msgstr "Cuir isteach an soláthraí óstála is fearr leat"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Cuir isteach do leasainm" msgstr "Cuir isteach do leasainm"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." 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 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Do leasainm iomlán anseo:" msgstr "Do leasainm iomlán anseo:"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Seolfar do thuairisc go dtí Seirbhís Modhnóireachta Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Do leasainm" msgstr "Do leasainm"
+3 -3
View File
@@ -3896,7 +3896,7 @@ msgstr "Introduce o teu contrasinal"
#~ msgstr "Introduce o teu proveedor de aloxamento preferido" #~ msgstr "Introduce o teu proveedor de aloxamento preferido"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Introduce o teu alcume" msgstr "Introduce o teu alcume"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." msgstr "A seguinte canle está baleira! Sigue a máis persoas para ver o que está a acontecer."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "O teu alcume completo será" msgstr "O teu alcume completo será"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "O teu informe enviarase ao Servizo de moderación de Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "O teu alcume" msgstr "O teu alcume"
+3 -3
View File
@@ -3780,7 +3780,7 @@ msgstr "अपना पासवर्ड दर्ज करें"
#~ msgstr "अपना पसंदीदा होस्टिंग प्रदाता दर्ज करें" #~ msgstr "अपना पसंदीदा होस्टिंग प्रदाता दर्ज करें"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "अपना उपयोगकर्ता हैंडल दर्ज करें" msgstr "अपना उपयोगकर्ता हैंडल दर्ज करें"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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 "आपका फ़ॉलोइंग फ़ीड खाली है! क्या चल रहा है जानने के लिए और उपयोगकर्ताओं को फ़ॉलो करें।" msgstr "आपका फ़ॉलोइंग फ़ीड खाली है! क्या चल रहा है जानने के लिए और उपयोगकर्ताओं को फ़ॉलो करें।"
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "आपका पूरा हैंडल होगा" msgstr "आपका पूरा हैंडल होगा"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -9070,5 +9070,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "आपके शिकायत को Bluesky मॉडरेशन सेवा को भेजा जाएगा।" msgstr "आपके शिकायत को Bluesky मॉडरेशन सेवा को भेजा जाएगा।"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "आपका उपयोगकर्ता हैंडल" msgstr "आपका उपयोगकर्ता हैंडल"
+3 -3
View File
@@ -3465,7 +3465,7 @@ msgstr "Jelszó megadása"
#~ msgstr "Kívánt tárhelyszolgáltató megadása" #~ msgstr "Kívánt tárhelyszolgáltató megadása"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Felhasználónév megadása" msgstr "Felhasználónév megadása"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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!" 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 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "A teljes felhasználóneved:" msgstr "A teljes felhasználóneved:"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Ez a jelentés a Bluesky moderálási szolgáltatásának lesz elküldve"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Saját felhasználónév" msgstr "Saját felhasználónév"
+3 -3
View File
@@ -4007,7 +4007,7 @@ msgstr "Masukkan kata sandi Anda"
#~ msgstr "Masukkan penyedia hosting pilihan Anda" #~ msgstr "Masukkan penyedia hosting pilihan Anda"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Masukkan panggilan Anda" msgstr "Masukkan panggilan Anda"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." msgstr "Feed mengikuti Anda kosong! Ikuti lebih banyak pengguna untuk melihat apa yang terjadi."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Panggilan lengkap Anda akan menjadi" msgstr "Panggilan lengkap Anda akan menjadi"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Laporan Anda akan dikirim ke Layanan Moderasi Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Panggilan Anda" msgstr "Panggilan Anda"
+3 -3
View File
@@ -3318,7 +3318,7 @@ msgid "Input your password"
msgstr "Inserisci la tua password" msgstr "Inserisci la tua password"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Inserisci il tuo nome utente" msgstr "Inserisci il tuo nome utente"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." msgstr "Il tuo feed seguente è vuoto! Segui più utenti per vedere cosa sta succedendo."
#: src/screens/Signup/StepHandle.tsx:125 #: 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à" msgstr "Il tuo nome utente completo sarà"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "La tua segnalazione verrà inviata al Servizio Moderazione di Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Il tuo nome utente" msgstr "Il tuo nome utente"
+3 -3
View File
@@ -3317,7 +3317,7 @@ msgid "Input your password"
msgstr "あなたのパスワードを入力" msgstr "あなたのパスワードを入力"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "あなたのユーザーハンドルを入力" msgstr "あなたのユーザーハンドルを入力"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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フィードは空です!もっと多くのユーザーをフォローして、近況を確認しましょう。" msgstr "Followingフィードは空です!もっと多くのユーザーをフォローして、近況を確認しましょう。"
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "フルハンドルは" msgstr "フルハンドルは"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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に送られます" msgstr "あなたの報告はBluesky Moderation Serviceに送られます"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "あなたのユーザーハンドル" msgstr "あなたのユーザーハンドル"
+3 -3
View File
@@ -3317,7 +3317,7 @@ msgid "Input your password"
msgstr "비밀번호를 입력합니다" msgstr "비밀번호를 입력합니다"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "사용자 핸들을 입력합니다" msgstr "사용자 핸들을 입력합니다"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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 "팔로우 중 피드가 비어 있습니다. 더 많은 사용자를 팔로우하여 무슨 일이 일어나고 있는지 확인하세요." msgstr "팔로우 중 피드가 비어 있습니다. 더 많은 사용자를 팔로우하여 무슨 일이 일어나고 있는지 확인하세요."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "내 전체 핸들:" msgstr "내 전체 핸들:"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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로 보내집니다." msgstr "신고가 Bluesky Moderation Service로 보내집니다."
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "내 사용자 핸들" msgstr "내 사용자 핸들"
+3 -3
View File
@@ -3496,7 +3496,7 @@ msgstr "Vul je wachtwoord in"
#~ msgstr "Vul je voorkeurshostingprovider in" #~ msgstr "Vul je voorkeurshostingprovider in"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Vul je gebruikershandle in." msgstr "Vul je gebruikershandle in."
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." msgstr "Je volgende feed is leeg! Volg meer gebruikers om te zien wat er gebeurt."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Je volledige handle wordt" msgstr "Je volledige handle wordt"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Je melding wordt verzonden naar de Bluesky-moderatieservice"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Je gebruikershandle" msgstr "Je gebruikershandle"
+3 -3
View File
@@ -3317,7 +3317,7 @@ msgid "Input your password"
msgstr "Wpisz hasło" msgstr "Wpisz hasło"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Wpisz nazwę użytkownika" msgstr "Wpisz nazwę użytkownika"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." msgstr "Twój kanał osób obserwowanych jest pusty! Zaobserwuj trochę więcej osób."
#: src/screens/Signup/StepHandle.tsx:125 #: 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" msgstr "Twoją pełna nazwa będzie"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Zgłoszenie będzie wysłane do usługi moderacji Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Twoja nazwa użytkownika" msgstr "Twoja nazwa użytkownika"
+3 -3
View File
@@ -4003,7 +4003,7 @@ msgstr "Insira sua senha"
#~ msgstr "Insira seu provedor de hospedagem" #~ msgstr "Insira seu provedor de hospedagem"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Insira o usuário" msgstr "Insira o usuário"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." msgstr "Seu feed inicial está vazio! Siga mais usuários para acompanhar o que está acontecendo."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Seu identificador completo será" msgstr "Seu identificador completo será"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "Sua denúncia será enviada para o serviço de moderação do Bluesky"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Seu identificador de usuário" msgstr "Seu identificador de usuário"
+3 -3
View File
@@ -3578,7 +3578,7 @@ msgstr "Введите ваш пароль"
#~ msgstr "Введите желаемого хостинг-провайдера" #~ msgstr "Введите желаемого хостинг-провайдера"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Введите ваш псевдоним" msgstr "Введите ваш псевдоним"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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 "Ваша домашняя лента пуста! Подпишитесь на больше пользователей чтобы получать больше постов." msgstr "Ваша домашняя лента пуста! Подпишитесь на больше пользователей чтобы получать больше постов."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Ваш полный псевдоним будет" msgstr "Ваш полный псевдоним будет"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -8812,5 +8812,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "Ваш отчет будет отправлен в Службу Модерации Bluesky." msgstr "Ваш отчет будет отправлен в Службу Модерации Bluesky."
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Ваш псевдоним" msgstr "Ваш псевдоним"
+3 -3
View File
@@ -3994,7 +3994,7 @@ msgstr "ป้อนรหัสผ่านของคุณ"
#~ msgstr "กรอกผู้ให้บริการโฮสติ้งที่คุณต้องการ" #~ msgstr "กรอกผู้ให้บริการโฮสติ้งที่คุณต้องการ"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "กรอกชื่อผู้ใช้ของคุณ" msgstr "กรอกชื่อผู้ใช้ของคุณ"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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 "ฟีดผู้ติดตามของคุณว่างเปล่า! ติดตามผู้ใช้เพิ่มเติมเพื่อดูความเคลื่อนไหว" msgstr "ฟีดผู้ติดตามของคุณว่างเปล่า! ติดตามผู้ใช้เพิ่มเติมเพื่อดูความเคลื่อนไหว"
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "ชื่อผู้ใช้เต็มของคุณจะเป็น" msgstr "ชื่อผู้ใช้เต็มของคุณจะเป็น"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" msgstr "รายงานของคุณจะถูกส่งไปยัง Bluesky Moderation Service"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "แฮนด์เดิลผู้ใช้ของคุณ" msgstr "แฮนด์เดิลผู้ใช้ของคุณ"
+3 -3
View File
@@ -4211,7 +4211,7 @@ msgstr "Şifrenizi girin"
#~ msgstr "" #~ msgstr ""
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Kullanıcı adınızı girin" msgstr "Kullanıcı adınızı girin"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." 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 #: 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" msgstr "Tam kullanıcı adınız"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -10261,5 +10261,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "" msgstr ""
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Kullanıcı adınız" msgstr "Kullanıcı adınız"
+3 -3
View File
@@ -4007,7 +4007,7 @@ msgstr "Введіть ваш пароль"
#~ msgstr "Введіть бажаного хостинг-провайдера" #~ msgstr "Введіть бажаного хостинг-провайдера"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "Введіть ваш псевдонім" msgstr "Введіть ваш псевдонім"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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 "Ваша домашня стрічка порожня! Підпишіться на більше користувачів щоб отримувати більше постів." msgstr "Ваша домашня стрічка порожня! Підпишіться на більше користувачів щоб отримувати більше постів."
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "Ваш повний псевдонім буде" msgstr "Ваш повний псевдонім буде"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -9722,5 +9722,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "" msgstr ""
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "Ваш псевдонім" msgstr "Ваш псевдонім"
+3 -3
View File
@@ -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" #~ 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 #: 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" msgstr "Nhập tên người dùng của bạn"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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." 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 #: 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à" msgstr "Tên người dùng đầy đủ của bạn sẽ là"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: 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" 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 #: 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" msgstr "Tên người dùng của bạn"
+3 -3
View File
@@ -3314,7 +3314,7 @@ msgid "Input your password"
msgstr "输入你的密码" msgstr "输入你的密码"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "输入你的账户代码" msgstr "输入你的账户代码"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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”动态源是空的!关注更多用户去看看他们发了什么。" msgstr "你的“Following”动态源是空的!关注更多用户去看看他们发了什么。"
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "你的完整账户代码将修改为" msgstr "你的完整账户代码将修改为"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "你的举报将发送至 Bluesky 内容审核服务" msgstr "你的举报将发送至 Bluesky 内容审核服务"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "你的账户代码" msgstr "你的账户代码"
+3 -3
View File
@@ -3314,7 +3314,7 @@ msgid "Input your password"
msgstr "輸入你嘅密碼" msgstr "輸入你嘅密碼"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "輸入你嘅帳號頭銜" msgstr "輸入你嘅帳號頭銜"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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」動態源得個吉!跟多啲用戶睇吓發生緊啲咩事。" msgstr "你嘅「Following」動態源得個吉!跟多啲用戶睇吓發生緊啲咩事。"
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "你嘅完整帳號頭銜會係" msgstr "你嘅完整帳號頭銜會係"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "你嘅上報會傳送去 Bluesky 審核服務" msgstr "你嘅上報會傳送去 Bluesky 審核服務"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "你嘅帳號頭銜" msgstr "你嘅帳號頭銜"
+3 -3
View File
@@ -3314,7 +3314,7 @@ msgid "Input your password"
msgstr "輸入您的密碼" msgstr "輸入您的密碼"
#: src/screens/Signup/StepHandle.tsx:114 #: src/screens/Signup/StepHandle.tsx:114
msgid "Input your user handle" msgid "Type your desired username"
msgstr "輸入您的帳號代碼" msgstr "輸入您的帳號代碼"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49 #: 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」動態源是空的!跟隨更多用戶來看看發生了什麼事情。" msgstr "您的「Following」動態源是空的!跟隨更多用戶來看看發生了什麼事情。"
#: src/screens/Signup/StepHandle.tsx:125 #: src/screens/Signup/StepHandle.tsx:125
msgid "Your full handle will be" msgid "Your full username will be"
msgstr "您的完整帳號代碼將修改為" msgstr "您的完整帳號代碼將修改為"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219 #: src/screens/Settings/components/ChangeHandleDialog.tsx:219
@@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
msgstr "您的檢舉將傳送至 Bluesky 內容管理服務" msgstr "您的檢舉將傳送至 Bluesky 內容管理服務"
#: src/screens/Signup/index.tsx:142 #: src/screens/Signup/index.tsx:142
msgid "Your user handle" msgid "Choose your username"
msgstr "您的帳號代碼" msgstr "您的帳號代碼"
+2 -3
View File
@@ -3,8 +3,7 @@ import {
error as bdError, error as bdError,
info as bdInfo, info as bdInfo,
warn as bdWarn, warn as bdWarn,
} from '@bitdrift/react-native' } from '../lib/bitdrift'
import {LogLevel, Transport} from './types' import {LogLevel, Transport} from './types'
export function createBitdriftTransport(): Transport { export function createBitdriftTransport(): Transport {
@@ -18,6 +17,6 @@ export function createBitdriftTransport(): Transport {
return (level, message) => { return (level, message) => {
const log = logFunctions[level] const log = logFunctions[level]
log(message.toString()) log('' + message)
} }
} }
-7
View File
@@ -1,7 +0,0 @@
import {Transport} from './index'
export function createBitdriftTransport(): Transport {
return (_level, _message) => {
// noop
}
}
+6 -2
View File
@@ -14,7 +14,7 @@ import {
TIMELINE_SAVED_FEED, TIMELINE_SAVED_FEED,
} from '#/lib/constants' } from '#/lib/constants'
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent, useGate} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
import {getAllListMembers} from '#/state/queries/list-members' import {getAllListMembers} from '#/state/queries/list-members'
@@ -57,6 +57,7 @@ export function StepFinished() {
const setActiveStarterPack = useSetActiveStarterPack() const setActiveStarterPack = useSetActiveStarterPack()
const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack() const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack()
const {startProgressGuide} = useProgressGuideControls() const {startProgressGuide} = useProgressGuideControls()
const gate = useGate()
const finishOnboarding = React.useCallback(async () => { const finishOnboarding = React.useCallback(async () => {
setSaving(true) setSaving(true)
@@ -190,7 +191,9 @@ export function StepFinished() {
setSaving(false) setSaving(false)
setActiveStarterPack(undefined) setActiveStarterPack(undefined)
setHasCheckedForStarterPack(true) setHasCheckedForStarterPack(true)
startProgressGuide('like-10-and-follow-7') startProgressGuide(
gate('new_postonboarding') ? 'follow-10' : 'like-10-and-follow-7',
)
dispatch({type: 'finish'}) dispatch({type: 'finish'})
onboardDispatch({type: 'finish'}) onboardDispatch({type: 'finish'})
logEvent('onboarding:finished:nextPressed', { logEvent('onboarding:finished:nextPressed', {
@@ -221,6 +224,7 @@ export function StepFinished() {
setActiveStarterPack, setActiveStarterPack,
setHasCheckedForStarterPack, setHasCheckedForStarterPack,
startProgressGuide, startProgressGuide,
gate,
]) ])
return ( return (
+13
View File
@@ -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() { export function useInterestsDisplayNames() {
const {_} = useLingui() 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 {Trans} from '@lingui/macro'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import {useTrendingSettings} from '#/state/preferences/trending'
import { import {
DEFAULT_LIMIT as TRENDING_TOPICS_COUNT, DEFAULT_LIMIT as TRENDING_TOPICS_COUNT,
useTrendingTopics, useTrendingTopics,
@@ -19,7 +20,8 @@ import {Text} from '#/components/Typography'
export function ExploreTrendingTopics() { export function ExploreTrendingTopics() {
const {enabled} = useTrendingConfig() const {enabled} = useTrendingConfig()
return enabled ? <Inner /> : null const {trendingDisabled} = useTrendingSettings()
return enabled && !trendingDisabled ? <Inner /> : null
} }
function Inner() { function Inner() {
@@ -56,14 +58,20 @@ function Inner() {
</View> </View>
</View> </View>
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}> <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> </Text>
</View> </View>
</View> </View>
<View style={[a.pt_md, a.pb_lg]}> <View style={[a.pt_md, a.pb_lg]}>
<View <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 ? ( {isLoading ? (
Array(TRENDING_TOPICS_COUNT) Array(TRENDING_TOPICS_COUNT)
.fill(0) .fill(0)
@@ -5,11 +5,11 @@ import {useLingui} from '@lingui/react'
import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon' import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon'
import {NativeStackScreenProps} from '@react-navigation/native-stack' 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 {PressableScale} from '#/lib/custom-animations/PressableScale'
import {CommonNavigatorParams} from '#/lib/routes/types' import {CommonNavigatorParams} from '#/lib/routes/types'
import {useGate} from '#/lib/statsig/statsig'
import {isAndroid} from '#/platform/detection' import {isAndroid} from '#/platform/detection'
import {useSession} from '#/state/session'
import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage'
import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' import {AppIconSet} from '#/screens/Settings/AppIconSettings/types'
import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets'
@@ -23,7 +23,7 @@ export function AppIconSettingsScreen({}: Props) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const sets = useAppIconSets() const sets = useAppIconSets()
const {currentAccount} = useSession() const gate = useGate()
const [currentAppIcon, setCurrentAppIcon] = useState(() => const [currentAppIcon, setCurrentAppIcon] = useState(() =>
getAppIconName(DynamicAppIcon.getAppIcon()), getAppIconName(DynamicAppIcon.getAppIcon()),
) )
@@ -86,7 +86,7 @@ export function AppIconSettingsScreen({}: Props) {
))} ))}
</Group> </Group>
{DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( {IS_INTERNAL && gate('debug_subscriptions') && (
<> <>
<Text <Text
style={[ style={[
+4 -5
View File
@@ -8,10 +8,10 @@ import Animated, {
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' 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 {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {useGate} from '#/lib/statsig/statsig'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {useSession} from '#/state/session'
import {useSetThemePrefs, useThemePrefs} from '#/state/shell' import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem' import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem'
import {atoms as a, native, useAlf, useTheme} from '#/alf' import {atoms as a, native, useAlf, useTheme} from '#/alf'
@@ -29,6 +29,7 @@ type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'>
export function AppearanceSettingsScreen({}: Props) { export function AppearanceSettingsScreen({}: Props) {
const {_} = useLingui() const {_} = useLingui()
const {fonts} = useAlf() const {fonts} = useAlf()
const gate = useGate()
const {colorMode, darkTheme} = useThemePrefs() const {colorMode, darkTheme} = useThemePrefs()
const {setColorMode, setDarkTheme} = useSetThemePrefs() const {setColorMode, setDarkTheme} = useSetThemePrefs()
@@ -74,8 +75,6 @@ export function AppearanceSettingsScreen({}: Props) {
[fonts], [fonts],
) )
const {currentAccount} = useSession()
return ( return (
<LayoutAnimationConfig skipExiting skipEntering> <LayoutAnimationConfig skipExiting skipEntering>
<Layout.Screen testID="preferencesThreadsScreen"> <Layout.Screen testID="preferencesThreadsScreen">
@@ -178,7 +177,7 @@ export function AppearanceSettingsScreen({}: Props) {
onChange={onChangeFontScale} onChange={onChangeFontScale}
/> />
{isNative && DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( {isNative && IS_INTERNAL && gate('debug_subscriptions') && (
<> <>
<SettingsList.Divider /> <SettingsList.Divider />
<AppIconSettingsListItem /> <AppIconSettingsListItem />
@@ -36,9 +36,8 @@ export function ContentAndMediaSettingsScreen({}: Props) {
const inAppBrowserPref = useInAppBrowser() const inAppBrowserPref = useInAppBrowser()
const setUseInAppBrowser = useSetInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser()
const {enabled: trendingEnabled} = useTrendingConfig() const {enabled: trendingEnabled} = useTrendingConfig()
const {trendingSidebarHidden, trendingDiscoverHidden} = useTrendingSettings() const {trendingDisabled} = useTrendingSettings()
const {setTrendingSidebarHidden, setTrendingDiscoverHidden} = const {setTrendingDisabled} = useTrendingSettingsApi()
useTrendingSettingsApi()
return ( return (
<Layout.Screen> <Layout.Screen>
@@ -117,30 +116,15 @@ export function ContentAndMediaSettingsScreen({}: Props) {
{trendingEnabled && ( {trendingEnabled && (
<> <>
<SettingsList.Divider /> <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 <Toggle.Item
name="show_trending_topics_discover" name="show_trending_topics"
label={_(msg`Show trending topics in your feed`)} label={_(msg`Enable trending topics`)}
value={!trendingDiscoverHidden} value={!trendingDisabled}
onChange={value => setTrendingDiscoverHidden(!value)}> onChange={value => setTrendingDisabled(!value)}>
<SettingsList.Item> <SettingsList.Item>
<SettingsList.ItemIcon icon={Graph} /> <SettingsList.ItemIcon icon={Graph} />
<SettingsList.ItemText> <SettingsList.ItemText>
<Trans>Show trending topics in your feed</Trans> <Trans>Enable trending topics</Trans>
</SettingsList.ItemText> </SettingsList.ItemText>
<Toggle.Platform /> <Toggle.Platform />
</SettingsList.Item> </SettingsList.Item>
+2 -2
View File
@@ -111,7 +111,7 @@ export function StepHandle() {
handleValueRef.current = val handleValueRef.current = val
setDraftValue(val) setDraftValue(val)
}} }}
label={_(msg`Input your user handle`)} label={_(msg`Type your desired username`)}
defaultValue={draftValue} defaultValue={draftValue}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
@@ -122,7 +122,7 @@ export function StepHandle() {
</View> </View>
{draftValue !== '' && ( {draftValue !== '' && (
<Text style={[a.text_md]}> <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]}> <Text style={[a.text_md, a.font_bold]}>
@{createFullHandle(draftValue, state.userDomain)} @{createFullHandle(draftValue, state.userDomain)}
</Text> </Text>
+1 -1
View File
@@ -139,7 +139,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
{state.activeStep === SignupStep.INFO ? ( {state.activeStep === SignupStep.INFO ? (
<Trans>Your account</Trans> <Trans>Your account</Trans>
) : state.activeStep === SignupStep.HANDLE ? ( ) : state.activeStep === SignupStep.HANDLE ? (
<Trans>Your user handle</Trans> <Trans>Choose your username</Trans>
) : ( ) : (
<Trans>Complete the challenge</Trans> <Trans>Complete the challenge</Trans>
)} )}
+12
View File
@@ -24,6 +24,7 @@ const _emitter = new EventEmitter()
export async function init() { export async function init() {
broadcast.onmessage = onBroadcastMessage broadcast.onmessage = onBroadcastMessage
window.onstorage = onStorage
const stored = readFromStorage() const stored = readFromStorage()
if (stored) { if (stored) {
_state = stored _state = stored
@@ -90,6 +91,17 @@ export async function clearStorage() {
} }
clearStorage satisfies PersistedApi['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) { async function onBroadcastMessage({data}: MessageEvent) {
if ( if (
typeof data === 'object' && typeof data === 'object' &&
+2 -12
View File
@@ -125,12 +125,7 @@ const schema = z.object({
subtitlesEnabled: z.boolean().optional(), subtitlesEnabled: z.boolean().optional(),
/** @deprecated */ /** @deprecated */
mutedThreads: z.array(z.string()), mutedThreads: z.array(z.string()),
trendingDisabled: z.boolean().optional(),
/*
* Trending
*/
trendingSidebarHidden: z.boolean().optional(),
trendingDiscoverHidden: z.boolean().optional(),
}) })
export type Schema = z.infer<typeof schema> export type Schema = z.infer<typeof schema>
@@ -176,12 +171,7 @@ export const defaults: Schema = {
kawaii: false, kawaii: false,
hasCheckedForStarterPack: false, hasCheckedForStarterPack: false,
subtitlesEnabled: true, subtitlesEnabled: true,
trendingDisabled: false,
/*
* Trending
*/
trendingSidebarHidden: false,
trendingDiscoverHidden: false,
} }
export function tryParse(rawData: string): Schema | undefined { export function tryParse(rawData: string): Schema | undefined {
+10 -27
View File
@@ -3,31 +3,19 @@ import React from 'react'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
type StateContext = { type StateContext = {
trendingSidebarHidden: Exclude< trendingDisabled: Exclude<persisted.Schema['trendingDisabled'], undefined>
persisted.Schema['trendingSidebarHidden'],
undefined
>
trendingDiscoverHidden: Exclude<
persisted.Schema['trendingDiscoverHidden'],
undefined
>
} }
type ApiContext = { type ApiContext = {
setTrendingSidebarHidden( setTrendingDisabled(
hidden: Exclude<persisted.Schema['trendingSidebarHidden'], undefined>, hidden: Exclude<persisted.Schema['trendingDisabled'], undefined>,
): void
setTrendingDiscoverHidden(
hidden: Exclude<persisted.Schema['trendingDiscoverHidden'], undefined>,
): void ): void
} }
const StateContext = React.createContext<StateContext>({ const StateContext = React.createContext<StateContext>({
trendingSidebarHidden: Boolean(persisted.defaults.trendingSidebarHidden), trendingDisabled: Boolean(persisted.defaults.trendingDisabled),
trendingDiscoverHidden: Boolean(persisted.defaults.trendingDiscoverHidden),
}) })
const ApiContext = React.createContext<ApiContext>({ const ApiContext = React.createContext<ApiContext>({
setTrendingSidebarHidden() {}, setTrendingDisabled() {},
setTrendingDiscoverHidden() {},
}) })
function usePersistedBooleanValue<T extends keyof persisted.Schema>(key: T) { 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<{}>) { export function Provider({children}: React.PropsWithChildren<{}>) {
const [trendingSidebarHidden, setTrendingSidebarHidden] = const [trendingDisabled, setTrendingDisabled] =
usePersistedBooleanValue('trendingSidebarHidden') usePersistedBooleanValue('trendingDisabled')
const [trendingDiscoverHidden, setTrendingDiscoverHidden] =
usePersistedBooleanValue('trendingDiscoverHidden')
/* /*
* Context * Context
*/ */
const state = React.useMemo( const state = React.useMemo(() => ({trendingDisabled}), [trendingDisabled])
() => ({trendingSidebarHidden, trendingDiscoverHidden}),
[trendingSidebarHidden, trendingDiscoverHidden],
)
const api = React.useMemo( const api = React.useMemo(
() => ({setTrendingSidebarHidden, setTrendingDiscoverHidden}), () => ({setTrendingDisabled}),
[setTrendingSidebarHidden, setTrendingDiscoverHidden], [setTrendingDisabled],
) )
return ( return (
+22 -4
View File
@@ -1,6 +1,7 @@
import {AppBskyActorDefs, AppBskyActorSearchActors} from '@atproto/api' import {AppBskyActorDefs, AppBskyActorSearchActors} from '@atproto/api'
import { import {
InfiniteData, InfiniteData,
keepPreviousData,
QueryClient, QueryClient,
QueryKey, QueryKey,
useInfiniteQuery, useInfiniteQuery,
@@ -13,10 +14,8 @@ import {useAgent} from '#/state/session'
const RQKEY_ROOT = 'actor-search' const RQKEY_ROOT = 'actor-search'
export const RQKEY = (query: string) => [RQKEY_ROOT, query] export const RQKEY = (query: string) => [RQKEY_ROOT, query]
export const RQKEY_PAGINATED = (query: string) => [ const RQKEY_ROOT_PAGINATED = `${RQKEY_ROOT}_paginated`
`${RQKEY_ROOT}_paginated`, export const RQKEY_PAGINATED = (query: string) => [RQKEY_ROOT_PAGINATED, query]
query,
]
export function useActorSearch({ export function useActorSearch({
query, query,
@@ -42,9 +41,11 @@ export function useActorSearch({
export function useActorSearchPaginated({ export function useActorSearchPaginated({
query, query,
enabled, enabled,
maintainData,
}: { }: {
query: string query: string
enabled?: boolean enabled?: boolean
maintainData?: boolean
}) { }) {
const agent = useAgent() const agent = useAgent()
return useInfiniteQuery< return useInfiniteQuery<
@@ -67,6 +68,7 @@ export function useActorSearchPaginated({
enabled: enabled && !!query, enabled: enabled && !!query,
initialPageParam: undefined, initialPageParam: undefined,
getNextPageParam: lastPage => lastPage.cursor, 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
}
}
}
} }
+1 -4
View File
@@ -144,11 +144,8 @@ export function usePostFeedQuery(
/** /**
* The number of posts to fetch in a single request. Because we filter * 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 * 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 const fetchLimit = MIN_POSTS
// Make sure this doesn't invalidate unless really needed. // Make sure this doesn't invalidate unless really needed.
+8 -1
View File
@@ -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() const agent = useAgent()
return useQuery({ return useQuery({
queryKey: suggestedFollowsByActorQueryKey(did), queryKey: suggestedFollowsByActorQueryKey(did),
@@ -116,6 +122,7 @@ export function useSuggestedFollowsByActorQuery({did}: {did: string}) {
: res.data.suggestions.filter(profile => !profile.viewer?.following) : res.data.suggestions.filter(profile => !profile.viewer?.following)
return {suggestions} return {suggestions}
}, },
enabled,
}) })
} }
@@ -4,25 +4,12 @@ import {describe, expect, it, jest} from '@jest/globals'
import {agentToSessionAccountOrThrow} from '../agent' import {agentToSessionAccountOrThrow} from '../agent'
import {Action, getInitialState, reducer, State} from '../reducer' import {Action, getInitialState, reducer, State} from '../reducer'
jest.mock('statsig-react-native-expo', () => ({
Statsig: {
initialize() {},
initializeCalled() {
return false
},
},
}))
jest.mock('jwt-decode', () => ({ jest.mock('jwt-decode', () => ({
jwtDecode(_token: string) { jwtDecode(_token: string) {
return {} return {}
}, },
})) }))
jest.mock('expo-localization', () => ({
getLocales: () => [],
}))
describe('session', () => { describe('session', () => {
it('can log in and out', () => { it('can log in and out', () => {
let state = getInitialState([]) let state = getInitialState([])
+78 -18
View File
@@ -1,4 +1,4 @@
import React from 'react' import React, {useMemo} from 'react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -16,20 +16,32 @@ export enum ProgressGuideAction {
Follow = 'follow', 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 { interface BaseProgressGuide {
guide: string guide: ProgressGuideName
isComplete: boolean isComplete: boolean
[key: string]: any [key: string]: any
} }
interface Like10AndFollow7ProgressGuide extends BaseProgressGuide { export interface Like10AndFollow7ProgressGuide extends BaseProgressGuide {
guide: 'like-10-and-follow-7'
numLikes: number numLikes: number
numFollows: 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) const ProgressGuideContext = React.createContext<ProgressGuide>(undefined)
@@ -61,15 +73,28 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
const {mutateAsync, variables, isPending} = const {mutateAsync, variables, isPending} =
useSetActiveProgressGuideMutation() useSetActiveProgressGuideMutation()
const activeProgressGuide = ( const activeProgressGuide = useMemo(() => {
isPending ? variables : preferences?.bskyAppState?.activeProgressGuide const rawProgressGuide = (
) as ProgressGuide isPending ? variables : preferences?.bskyAppState?.activeProgressGuide
) as ProgressGuide
// ensure the unspecced attributes have the correct types if (!rawProgressGuide) return undefined
if (activeProgressGuide?.guide === 'like-10-and-follow-7') {
activeProgressGuide.numLikes = Number(activeProgressGuide.numLikes) || 0 // ensure the unspecced attributes have the correct types
activeProgressGuide.numFollows = Number(activeProgressGuide.numFollows) || 0 // 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] = const [localGuideState, setLocalGuideState] =
React.useState<ProgressGuide>(undefined) React.useState<ProgressGuide>(undefined)
@@ -82,7 +107,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
const firstLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null) const firstLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null)
const fifthLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null) const fifthLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null)
const tenthLikeToastRef = 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(() => { const controls = React.useMemo(() => {
return { return {
@@ -93,7 +120,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
numLikes: 0, numLikes: 0,
numFollows: 0, numFollows: 0,
isComplete: false, 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) setLocalGuideState(guideObj)
mutateAsync(guideObj) mutateAsync(guideObj)
} }
@@ -137,6 +172,26 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
isComplete: true, 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) setLocalGuideState(guide)
@@ -167,9 +222,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
subtitle={_(msg`The Discover feed now knows what you like`)} subtitle={_(msg`The Discover feed now knows what you like`)}
/> />
<ProgressGuideToast <ProgressGuideToast
ref={guideCompleteToastRef} ref={fifthFollowToastRef}
title={_(msg`Algorithm training complete!`)} title={_(msg`Half way there!`)}
subtitle={_(msg`The Discover feed now knows what you like`)} 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'} highlightUnread={filter === 'all'}
item={item} item={item}
moderationOpts={moderationOpts!} moderationOpts={moderationOpts!}
hideTopBorder={index === 0 && item.notification.isRead} hideTopBorder={index === 0}
/> />
) )
}, },
+1 -1
View File
@@ -145,7 +145,7 @@ export function TabBar({
const desktopStyles = StyleSheet.create({ const desktopStyles = StyleSheet.create({
outer: { outer: {
flexDirection: 'row', flexDirection: 'row',
width: 598, width: 600,
}, },
contentContainer: { contentContainer: {
flexGrow: 1, flexGrow: 1,
+23 -15
View File
@@ -35,6 +35,7 @@ import {
} from '#/state/queries/post-feed' } from '#/state/queries/post-feed'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useProgressGuide} from '#/state/shell/progress-guide' import {useProgressGuide} from '#/state/shell/progress-guide'
import {useBreakpoints} from '#/alf'
import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials' import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials'
import {TrendingInterstitial} from '#/components/interstitials/Trending' import {TrendingInterstitial} from '#/components/interstitials/Trending'
import {List, ListRef} from '../util/List' import {List, ListRef} from '../util/List'
@@ -162,6 +163,7 @@ let PostFeed = ({
const checkForNewRef = React.useRef<(() => void) | null>(null) const checkForNewRef = React.useRef<(() => void) | null>(null)
const lastFetchRef = React.useRef<number>(Date.now()) const lastFetchRef = React.useRef<number>(Date.now())
const [feedType, feedUri, feedTab] = feed.split('|') const [feedType, feedUri, feedTab] = feed.split('|')
const {gtTablet} = useBreakpoints()
const opts = React.useMemo( const opts = React.useMemo(
() => ({enabled, ignoreFilterFor}), () => ({enabled, ignoreFilterFor}),
@@ -193,12 +195,16 @@ let PostFeed = ({
} }
try { try {
if (await pollLatest(data.pages[0])) { if (await pollLatest(data.pages[0])) {
onHasNew(true) if (isEmpty) {
refetch()
} else {
onHasNew(true)
}
} }
} catch (e) { } catch (e) {
logger.error('Poll latest failed', {feed, message: String(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 myDid = currentAccount?.did || ''
const onPostCreated = React.useCallback(() => { const onPostCreated = React.useCallback(() => {
@@ -226,20 +232,15 @@ let PostFeed = ({
React.useEffect(() => { React.useEffect(() => {
if (enabled && !disablePoll) { if (enabled && !disablePoll) {
const timeSinceFirstLoad = Date.now() - lastFetchRef.current const timeSinceFirstLoad = Date.now() - lastFetchRef.current
// DISABLED need to check if this is causing random feed refreshes -prf if (
/*if (timeSinceFirstLoad > REFRESH_AFTER) { (isEmpty || timeSinceFirstLoad > CHECK_LATEST_AFTER) &&
// do a full refresh
scrollElRef?.current?.scrollToOffset({offset: 0, animated: false})
queryClient.resetQueries({queryKey: RQKEY(feed)})
} else*/ if (
timeSinceFirstLoad > CHECK_LATEST_AFTER &&
checkForNewRef.current checkForNewRef.current
) { ) {
// check for new on enable (aka on focus) // check for new on enable (aka on focus)
checkForNewRef.current() checkForNewRef.current()
} }
} }
}, [enabled, disablePoll, feed, queryClient, scrollElRef]) }, [enabled, disablePoll, feed, queryClient, scrollElRef, isEmpty])
React.useEffect(() => { React.useEffect(() => {
let cleanup1: () => void | undefined, cleanup2: () => void | undefined let cleanup1: () => void | undefined, cleanup2: () => void | undefined
const subscription = AppState.addEventListener('change', nextAppState => { const subscription = AppState.addEventListener('change', nextAppState => {
@@ -260,11 +261,13 @@ let PostFeed = ({
} }
}, [pollInterval]) }, [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 {isDesktop} = useWebMediaQueries()
const showProgressIntersitial = progressGuide && !isDesktop const showProgressIntersitial =
(followProgressGuide || followAndLikeProgressGuide) && !isDesktop
const {trendingDiscoverHidden} = useTrendingSettings() const {trendingDisabled} = useTrendingSettings()
const feedItems: FeedRow[] = React.useMemo(() => { const feedItems: FeedRow[] = React.useMemo(() => {
let feedKind: 'following' | 'discover' | 'profile' | undefined let feedKind: 'following' | 'discover' | 'profile' | undefined
@@ -311,7 +314,11 @@ let PostFeed = ({
type: 'interstitialProgressGuide', type: 'interstitialProgressGuide',
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
}) })
} else if (sliceIndex === 15 && !trendingDiscoverHidden) { } else if (
sliceIndex === 15 &&
!gtTablet &&
!trendingDisabled
) {
arr.push({ arr.push({
type: 'interstitialTrending', type: 'interstitialTrending',
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
@@ -402,7 +409,8 @@ let PostFeed = ({
feedTab, feedTab,
hasSession, hasSession,
showProgressIntersitial, showProgressIntersitial,
trendingDiscoverHidden, trendingDisabled,
gtTablet,
]) ])
// events // events
+9 -1
View File
@@ -53,6 +53,7 @@ let List = React.forwardRef<ListMethods, ListProps>(
headerOffset, headerOffset,
style, style,
progressViewOffset, progressViewOffset,
automaticallyAdjustsScrollIndicatorInsets = false,
...props ...props
}, },
ref, ref,
@@ -151,7 +152,14 @@ let List = React.forwardRef<ListMethods, ListProps>(
return ( return (
<FlatList_INTERNAL <FlatList_INTERNAL
{...props} {...props}
scrollIndicatorInsets={{top: headerOffset, right: 1}} automaticallyAdjustsScrollIndicatorInsets={
automaticallyAdjustsScrollIndicatorInsets
}
scrollIndicatorInsets={{
top: headerOffset,
right: 1,
...props.scrollIndicatorInsets,
}}
contentOffset={contentOffset} contentOffset={contentOffset}
refreshControl={refreshControl} refreshControl={refreshControl}
onScroll={scrollHandler} onScroll={scrollHandler}
+3 -1
View File
@@ -196,7 +196,9 @@ function Toast({
/> />
</View> </View>
<View style={[a.h_full, a.justify_center, a.flex_1]}> <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>
</View> </View>
</GestureDetector> </GestureDetector>
+55 -82
View File
@@ -1,4 +1,4 @@
import {StyleSheet, View} from 'react-native' import {View} from 'react-native'
import { import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, FontAwesomeIconStyle,
@@ -7,12 +7,11 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {atoms as a, useTheme} from '#/alf'
import {useTheme} from '#/lib/ThemeContext' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {ViewHeader} from '#/view/com/util/ViewHeader' import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwiseIcon} from '#/components/icons/ArrowRotateCounterClockwise'
import {Button} from '../forms/Button' import * as Layout from '#/components/Layout'
import {Text} from '../text/Text' import {Text} from '#/components/Typography'
import {CenteredView} from '../Views'
export function ErrorScreen({ export function ErrorScreen({
title, title,
@@ -29,22 +28,32 @@ export function ErrorScreen({
testID?: string testID?: string
showHeader?: boolean showHeader?: boolean
}) { }) {
const theme = useTheme() const t = useTheme()
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
return ( return (
<> <Layout.Center testID={testID}>
{showHeader && isMobile && ( {showHeader && (
<ViewHeader title={_(msg`Error`)} showBorder /> <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={[a.px_xl, a.py_2xl]}>
<View style={styles.errorIconContainer}> <View style={[a.mb_md, a.align_center]}>
<View <View
style={[ style={[
styles.errorIcon, a.rounded_full,
{backgroundColor: theme.palette.inverted.background}, {width: 50, height: 50},
a.align_center,
a.justify_center,
{backgroundColor: t.palette.contrast_950},
]}> ]}>
<FontAwesomeIcon <FontAwesomeIcon
icon="exclamation" icon="exclamation"
@@ -53,86 +62,50 @@ export function ErrorScreen({
/> />
</View> </View>
</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} {title}
</Text> </Text>
<Text style={[styles.message, pal.text]}>{message}</Text> <Text style={[a.text_center, a.text_md, a.mb_xl]}>{message}</Text>
{details && ( {details && (
<Text <View
testID={`${testID}-details`} style={[
style={[styles.details, pal.text, pal.viewLight]}> a.w_full,
{details} a.border,
</Text> 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 && ( {onPressTryAgain && (
<View style={styles.btnContainer}> <View style={[a.align_center]}>
<Button <Button
testID="errorScreenTryAgainButton" testID="errorScreenTryAgainButton"
type="default"
style={[styles.btn]}
onPress={onPressTryAgain} onPress={onPressTryAgain}
accessibilityLabel={_(msg`Retry`)} variant="solid"
color="secondary_inverted"
size="small"
label={_(msg`Retry`)}
accessibilityHint={_( accessibilityHint={_(
msg`Retries the last action, which errored out`, msg`Retries the last action, which errored out`,
)}> )}>
<FontAwesomeIcon <ButtonIcon icon={ArrowRotateCounterClockwiseIcon} />
icon="arrows-rotate" <ButtonText>
style={pal.link as FontAwesomeIconStyle}
size={16}
/>
<Text type="button" style={[styles.btnText, pal.link]}>
<Trans context="action">Try again</Trans> <Trans context="action">Try again</Trans>
</Text> </ButtonText>
</Button> </Button>
</View> </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',
},
})
+4 -3
View File
@@ -18,12 +18,13 @@ import {msg, plural} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {IS_INTERNAL} from '#/lib/app-info' 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 {CountWheel} from '#/lib/custom-animations/CountWheel'
import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon' import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {shareUrl} from '#/lib/sharing' import {shareUrl} from '#/lib/sharing'
import {useGate} from '#/lib/statsig/statsig'
import {toShareUrl} from '#/lib/strings/url-helpers' import {toShareUrl} from '#/lib/strings/url-helpers'
import {Shadow} from '#/state/cache/types' import {Shadow} from '#/state/cache/types'
import {useFeedFeedbackContext} from '#/state/feed-feedback' import {useFeedFeedbackContext} from '#/state/feed-feedback'
@@ -85,8 +86,8 @@ let PostCtrls = ({
const {sendInteraction} = useFeedFeedbackContext() const {sendInteraction} = useFeedFeedbackContext()
const {captureAction} = useProgressGuideControls() const {captureAction} = useProgressGuideControls()
const playHaptic = useHaptics() const playHaptic = useHaptics()
const isDiscoverDebugUser = const gate = useGate()
IS_INTERNAL || DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] const isDiscoverDebugUser = IS_INTERNAL || gate('debug_show_feedcontext')
const isBlocked = Boolean( const isBlocked = Boolean(
post.author.viewer?.blocking || post.author.viewer?.blocking ||
post.author.viewer?.blockedBy || post.author.viewer?.blockedBy ||
+19 -14
View File
@@ -1,5 +1,6 @@
import React, {useCallback, useMemo} from 'react' import React, {useCallback, useMemo} from 'react'
import {StyleSheet} from 'react-native' import {StyleSheet} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'
import { import {
AppBskyActorDefs, AppBskyActorDefs,
AppBskyGraphGetActorStarterPacks, AppBskyGraphGetActorStarterPacks,
@@ -122,13 +123,15 @@ function ProfileScreenInner({route}: Props) {
} }
if (resolveError || profileError) { if (resolveError || profileError) {
return ( return (
<ErrorScreen <SafeAreaView style={[a.flex_1]}>
testID="profileErrorScreen" <ErrorScreen
title={profileError ? _(msg`Not Found`) : _(msg`Oops!`)} testID="profileErrorScreen"
message={cleanError(resolveError || profileError)} title={profileError ? _(msg`Not Found`) : _(msg`Oops!`)}
onPressTryAgain={onPressTryAgain} message={cleanError(resolveError || profileError)}
showHeader onPressTryAgain={onPressTryAgain}
/> showHeader
/>
</SafeAreaView>
) )
} }
if (profile && moderationOpts) { if (profile && moderationOpts) {
@@ -144,13 +147,15 @@ function ProfileScreenInner({route}: Props) {
} }
// should never happen // should never happen
return ( return (
<ErrorScreen <SafeAreaView style={[a.flex_1]}>
testID="profileErrorScreen" <ErrorScreen
title="Oops!" testID="profileErrorScreen"
message="Something went wrong and we're not sure what." title="Oops!"
onPressTryAgain={onPressTryAgain} message="Something went wrong and we're not sure what."
showHeader onPressTryAgain={onPressTryAgain}
/> showHeader
/>
</SafeAreaView>
) )
} }
+13
View File
@@ -24,6 +24,7 @@ import {
ProfileCardFeedLoadingPlaceholder, ProfileCardFeedLoadingPlaceholder,
} from '#/view/com/util/LoadingPlaceholder' } from '#/view/com/util/LoadingPlaceholder'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import {ExploreRecommendations} from '#/screens/Search/components/ExploreRecommendations'
import {ExploreTrendingTopics} from '#/screens/Search/components/ExploreTrendingTopics' import {ExploreTrendingTopics} from '#/screens/Search/components/ExploreTrendingTopics'
import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
import {Button} from '#/components/Button' import {Button} from '#/components/Button'
@@ -244,6 +245,10 @@ type ExploreScreenItems =
type: 'trendingTopics' type: 'trendingTopics'
key: string key: string
} }
| {
type: 'recommendations'
key: string
}
| { | {
type: 'profile' type: 'profile'
key: string key: string
@@ -337,6 +342,11 @@ export function Explore() {
key: `trending-topics`, key: `trending-topics`,
}) })
i.push({
type: 'recommendations',
key: `recommendations`,
})
i.push({ i.push({
type: 'header', type: 'header',
key: 'suggested-follows-header', key: 'suggested-follows-header',
@@ -503,6 +513,9 @@ export function Explore() {
case 'trendingTopics': { case 'trendingTopics': {
return <ExploreTrendingTopics /> return <ExploreTrendingTopics />
} }
case 'recommendations': {
return <ExploreRecommendations />
}
case 'profile': { case 'profile': {
return ( return (
<View style={[a.border_b, t.atoms.border_contrast_low]}> <View style={[a.border_b, t.atoms.border_contrast_low]}>
+29 -2
View File
@@ -14,7 +14,7 @@ import {createStaticClick, InlineLinkText} from '#/components/Link'
export function DesktopFeeds() { export function DesktopFeeds() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {data: pinnedFeedInfos} = usePinnedFeedsInfos() const {data: pinnedFeedInfos, error, isLoading} = usePinnedFeedsInfos()
const selectedFeed = useSelectedFeed() const selectedFeed = useSelectedFeed()
const setSelectedFeed = useSetSelectedFeed() const setSelectedFeed = useSetSelectedFeed()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
@@ -25,7 +25,34 @@ export function DesktopFeeds() {
return getCurrentRoute(state) 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 return null
} }
+23 -1
View File
@@ -1,6 +1,8 @@
import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/core'
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
@@ -15,6 +17,24 @@ import {InlineLinkText} from '#/components/Link'
import {ProgressGuideList} from '#/components/ProgressGuide/List' import {ProgressGuideList} from '#/components/ProgressGuide/List'
import {Text} from '#/components/Typography' 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}) { export function DesktopRightNav({routeName}: {routeName: string}) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
@@ -22,6 +42,8 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
const kawaii = useKawaiiMode() const kawaii = useKawaiiMode()
const gutters = useGutters(['base', 0, 'base', 'wide']) const gutters = useGutters(['base', 0, 'base', 'wide'])
const isSearchScreen = routeName === 'Search' const isSearchScreen = routeName === 'Search'
const {q: searchQuery} = useWebQueryParams()
const showTrending = !isSearchScreen || (isSearchScreen && !!searchQuery)
const {isTablet} = useWebMediaQueries() const {isTablet} = useWebMediaQueries()
if (isTablet) { 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]}> <Text style={[a.leading_snug, t.atoms.text_contrast_low]}>
{hasSession && ( {hasSession && (
@@ -6,10 +6,7 @@ import {
useTrendingSettings, useTrendingSettings,
useTrendingSettingsApi, useTrendingSettingsApi,
} from '#/state/preferences/trending' } from '#/state/preferences/trending'
import { import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics'
DEFAULT_LIMIT as TRENDING_TOPICS_COUNT,
useTrendingTopics,
} from '#/state/queries/trending/useTrendingTopics'
import {useTrendingConfig} from '#/state/trending-config' import {useTrendingConfig} from '#/state/trending-config'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
@@ -24,23 +21,25 @@ import {
} from '#/components/TrendingTopics' } from '#/components/TrendingTopics'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
const TRENDING_LIMIT = 6
export function SidebarTrendingTopics() { export function SidebarTrendingTopics() {
const {enabled} = useTrendingConfig() const {enabled} = useTrendingConfig()
const {trendingSidebarHidden} = useTrendingSettings() const {trendingDisabled} = useTrendingSettings()
return !enabled ? null : trendingSidebarHidden ? null : <Inner /> return !enabled ? null : trendingDisabled ? null : <Inner />
} }
function Inner() { function Inner() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const trendingPrompt = Prompt.usePromptControl() const trendingPrompt = Prompt.usePromptControl()
const {setTrendingSidebarHidden} = useTrendingSettingsApi() const {setTrendingDisabled} = useTrendingSettingsApi()
const {data: trending, error, isLoading} = useTrendingTopics() const {data: trending, error, isLoading} = useTrendingTopics()
const noTopics = !isLoading && !error && !trending?.topics?.length const noTopics = !isLoading && !error && !trending?.topics?.length
return error || noTopics ? null : ( 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]}> <View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Graph size="sm" /> <Graph size="sm" />
<Text <Text
@@ -53,7 +52,7 @@ function Inner() {
<Trans>Trending</Trans> <Trans>Trending</Trans>
</Text> </Text>
<Button <Button
label={_(msg`Hide trending topics from your sidebar`)} label={_(msg`Hide trending topics`)}
size="tiny" size="tiny"
variant="ghost" variant="ghost"
color="secondary" color="secondary"
@@ -63,16 +62,16 @@ function Inner() {
</Button> </Button>
</View> </View>
<View style={[a.flex_row, a.flex_wrap, a.gap_xs]}> <View style={[a.flex_row, a.flex_wrap, {gap: '6px 4px'}]}>
{isLoading ? ( {isLoading ? (
Array(TRENDING_TOPICS_COUNT) Array(TRENDING_LIMIT)
.fill(0) .fill(0)
.map((_n, i) => ( .map((_n, i) => (
<TrendingTopicSkeleton key={i} size="small" index={i} /> <TrendingTopicSkeleton key={i} size="small" index={i} />
)) ))
) : !trending?.topics ? null : ( ) : !trending?.topics ? null : (
<> <>
{trending.topics.map(topic => ( {trending.topics.slice(0, TRENDING_LIMIT).map(topic => (
<TrendingTopicLink key={topic.link} topic={topic}> <TrendingTopicLink key={topic.link} topic={topic}>
{({hovered}) => ( {({hovered}) => (
<TrendingTopic <TrendingTopic
@@ -95,11 +94,9 @@ function Inner() {
<Prompt.Basic <Prompt.Basic
control={trendingPrompt} control={trendingPrompt}
title={_(msg`Hide trending topics?`)} title={_(msg`Hide trending topics?`)}
description={_( description={_(msg`You can update this later from your settings.`)}
msg`This is a device setting, and will apply to all accounts on this device. You can update this later from your settings.`,
)}
confirmButtonCta={_(msg`Hide`)} confirmButtonCta={_(msg`Hide`)}
onConfirm={() => setTrendingSidebarHidden(true)} onConfirm={() => setTrendingDisabled(true)}
/> />
<Divider /> <Divider />
</> </>
+81 -76
View File
@@ -3696,10 +3696,10 @@
mv "~2" mv "~2"
safe-json-stringify "~1" safe-json-stringify "~1"
"@expo/cli@0.22.3": "@expo/cli@0.22.6":
version "0.22.3" version "0.22.6"
resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.22.3.tgz#8dbcc9396abf01b2dd91fe7f34fc23fdd2d8cc7f" resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.22.6.tgz#dc27b685d2252027549d839437c5285c7814ec3f"
integrity sha512-1HBtqInFDFHUJWzTJ1CJj5MR3JwvOiozmRUWF2kVQAeq/bKzSYM6We6B3XoZBM5XP6z6WtnrG87C7BjeW5E/cA== integrity sha512-eDjCnc3uHl2+SJ6aZ5seK0FkMp0W12oAdSI4A/yV8ecYtXzG8X87sfKAISEWt44B4DqJ0a1LEqCD6Vtvc783Mg==
dependencies: dependencies:
"@0no-co/graphql.web" "^1.0.8" "@0no-co/graphql.web" "^1.0.8"
"@babel/runtime" "^7.20.0" "@babel/runtime" "^7.20.0"
@@ -3710,15 +3710,15 @@
"@expo/env" "~0.4.0" "@expo/env" "~0.4.0"
"@expo/image-utils" "^0.6.0" "@expo/image-utils" "^0.6.0"
"@expo/json-file" "^9.0.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/osascript" "^2.0.31"
"@expo/package-manager" "^1.5.0" "@expo/package-manager" "^1.5.0"
"@expo/plist" "^0.2.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/rudder-sdk-node" "^1.1.1"
"@expo/spawn-async" "^1.7.2" "@expo/spawn-async" "^1.7.2"
"@expo/xcpretty" "^4.3.0" "@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/core" "^5.0.6"
"@urql/exchange-retry" "^1.3.0" "@urql/exchange-retry" "^1.3.0"
accepts "^1.3.8" accepts "^1.3.8"
@@ -3894,10 +3894,10 @@
dotenv-expand "~11.0.6" dotenv-expand "~11.0.6"
getenv "^1.0.0" getenv "^1.0.0"
"@expo/fingerprint@0.11.3": "@expo/fingerprint@0.11.4":
version "0.11.3" version "0.11.4"
resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.11.3.tgz#e370ae8f83e0642f752b058e2102e984a0a5bc98" resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.11.4.tgz#f40bbc784e10a065b783091e0d060a7428d41a7c"
integrity sha512-9lgXmcIePvZ7Wef63XtvuN3HfCUevF4E4tQPdEbH9/dUWwpOvvwQ3KT4OJ9jdh8JJ3nTdO9eDQ/8k8xr1aQ5Kg== integrity sha512-FfcvHjrWjOJ17wiMfr1iQ1YDyjlj8qfxG+GDce0khrjNSkzRjVdCOIFsMvfVSBPnOPX5NuZlgMRvMkcPUtGClA==
dependencies: dependencies:
"@expo/spawn-async" "^1.7.2" "@expo/spawn-async" "^1.7.2"
arg "^5.0.2" arg "^5.0.2"
@@ -3949,34 +3949,10 @@
json5 "^2.2.3" json5 "^2.2.3"
write-file-atomic "^2.3.0" write-file-atomic "^2.3.0"
"@expo/metro-config@0.19.6": "@expo/metro-config@0.19.8", "@expo/metro-config@~0.19.8":
version "0.19.6" version "0.19.8"
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.6.tgz#81bbe70c77a322d6c688738fd7b736a3cbb7c5bd" resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.8.tgz#f1ea552b6fa5217093fe364ff5ca78a7e261a28b"
integrity sha512-pRwZyOstsQa1+Ecss3wOqC28wjyjq9qxvJaQL3LH4G8Sef9x2PX+ySRApeQ01nl4ZN5nlyez6iVDF51tn/WhOw== integrity sha512-dVAOetouQYuOTEJ2zR0OTLNPOH6zPkeEt5fY53TK0Wxi1QmtsmH6vEWg05U4zkSJ6f1aXmQ0Za77R8QxuukESA==
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==
dependencies: dependencies:
"@babel/core" "^7.20.0" "@babel/core" "^7.20.0"
"@babel/generator" "^7.20.5" "@babel/generator" "^7.20.5"
@@ -4032,17 +4008,17 @@
base64-js "^1.2.3" base64-js "^1.2.3"
xmlbuilder "^14.0.0" xmlbuilder "^14.0.0"
"@expo/prebuild-config@8.0.22", "@expo/prebuild-config@^8.0.22": "@expo/prebuild-config@^8.0.23":
version "8.0.22" version "8.0.23"
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-8.0.22.tgz#6e2762a5f333a0519f83ae05b69da45e3e26a913" resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-8.0.23.tgz#2ec6d5464f35d308bdb94ba75b7e6aba0ebb507d"
integrity sha512-Kwlf3ymHH37W2nuNA9FzYgZvrImJScLA98939kapnOxfNGAPhmhEw26sfIGmBWAa8ymdL6p+HXQ3+b/xJ74bOg== integrity sha512-Zf01kFiN2PISmLb0DhIAJh76v3J2oYUKSjiAtGZLOH0HUz59by/qdyU4mGHWdeyRdCCrLUA21Rct2MBykvRMsg==
dependencies: dependencies:
"@expo/config" "~10.0.4" "@expo/config" "~10.0.4"
"@expo/config-plugins" "~9.0.10" "@expo/config-plugins" "~9.0.10"
"@expo/config-types" "^52.0.0" "@expo/config-types" "^52.0.0"
"@expo/image-utils" "^0.6.0" "@expo/image-utils" "^0.6.0"
"@expo/json-file" "^9.0.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" debug "^4.3.1"
fs-extra "^9.0.0" fs-extra "^9.0.0"
resolve-from "^5.0.0" resolve-from "^5.0.0"
@@ -5617,7 +5593,7 @@
dependencies: dependencies:
"@react-native/codegen" "0.76.1" "@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" version "0.76.1"
resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.76.1.tgz#2b9fd113e7c7889c1e87d6a36b7cb0f36118e7a6" resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.76.1.tgz#2b9fd113e7c7889c1e87d6a36b7cb0f36118e7a6"
integrity sha512-b6YRmA13CmVuTQKHRen/Q0glHwmZFZoEDs+MJ1NL0UNHq9V5ytvdwTW1ntkmjtXuTnPMzkwYvumJBN9UTZjkBA== 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" resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.76.3.tgz#531e616f6dad159a58117efc69cec20422d15b0d"
integrity sha512-pMHQ3NpPB28RxXciSvm2yD+uDx3pkhzfuWkc7VFgOduyzPSIr0zotUiOJzsAtrj8++bPbOsAraCeQhCqoOTWQw== 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": "@react-native/dev-middleware@0.76.3":
version "0.76.3" version "0.76.3"
resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.76.3.tgz#52edc76c88e0c2c436eb989551b827bf69f2a56f" 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" serve-static "^1.13.1"
ws "^6.2.3" 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": "@react-native/eslint-config@^0.76.2":
version "0.76.2" version "0.76.2"
resolved "https://registry.yarnpkg.com/@react-native/eslint-config/-/eslint-config-0.76.2.tgz#2741eee69ff194b8adc15281c0cb9695ba015ef0" 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" hermes-parser "0.23.1"
nullthrows "^1.1.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" version "0.76.1"
resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.76.1.tgz#df8d54d78917a9f075283382fec834f5ccaecefd" resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.76.1.tgz#df8d54d78917a9f075283382fec834f5ccaecefd"
integrity sha512-/+CUk/wGWIdXbJYVLw/q6Fs8Z0x91zzfXIbNiZUdSW1TNEDmytkF371H8a1/Nx3nWa1RqCMVsaZHCG4zqxeDvg== integrity sha512-/+CUk/wGWIdXbJYVLw/q6Fs8Z0x91zzfXIbNiZUdSW1TNEDmytkF371H8a1/Nx3nWa1RqCMVsaZHCG4zqxeDvg==
@@ -8089,10 +8087,10 @@ babel-preset-expo@^12.0.2:
babel-plugin-react-native-web "~0.19.13" babel-plugin-react-native-web "~0.19.13"
react-refresh "^0.14.2" react-refresh "^0.14.2"
babel-preset-expo@~12.0.3: babel-preset-expo@~12.0.4:
version "12.0.3" version "12.0.4"
resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-12.0.3.tgz#2ad62fe007517704841788cfea38b333e307663f" resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-12.0.4.tgz#ec965530d866c8905aac1fa478562cb08ab32a55"
integrity sha512-1695e8y3U/HjifKx33vcNnFMSUSXwPWwhFxRlL6NRx2TENN6gySH82gPOWgxcra6gi+EJgXx52xG3PcqTjwW6w== integrity sha512-SAzAwqpyjA+/OFrU95OOioj6oTeCv4+rRfrNmBTy5S/gJswrZKBSPJioFudIaJBy43W+BL7HA5AspBIF6tO/aA==
dependencies: dependencies:
"@babel/plugin-proposal-decorators" "^7.12.9" "@babel/plugin-proposal-decorators" "^7.12.9"
"@babel/plugin-transform-export-namespace-from" "^7.22.11" "@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/plugin-transform-parameters" "^7.22.15"
"@babel/preset-react" "^7.22.15" "@babel/preset-react" "^7.22.15"
"@babel/preset-typescript" "^7.23.0" "@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" babel-plugin-react-native-web "~0.19.13"
react-refresh "^0.14.2" 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" resolved "https://registry.yarnpkg.com/expo-eas-client/-/expo-eas-client-0.13.1.tgz#ebca627f3f58a54906394eb3f5d22f41a1822618"
integrity sha512-IyeDiM6YSJG0c45kbuEo0qt76z0KTEZtisEFEtle+b+vfn9I3N+r3jbPscaI4yS3P6gpuoDyHv81YDVC6Dmkhw== integrity sha512-IyeDiM6YSJG0c45kbuEo0qt76z0KTEZtisEFEtle+b+vfn9I3N+r3jbPscaI4yS3P6gpuoDyHv81YDVC6Dmkhw==
expo-file-system@^18.0.4, expo-file-system@~18.0.4: expo-file-system@^18.0.6, expo-file-system@~18.0.6:
version "18.0.4" version "18.0.6"
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.4.tgz#eecf8dc0b3b545e9ac5cd00352665afe2d57732f" resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.6.tgz#43f7718530d0e2aa1f49bca7ccb721007acabf2c"
integrity sha512-aAWEDwnu0XHOBYvQ9Q0+QIa+483vYJaC4IDsXyWQ73Rtsg273NZh5kYowY+cAocvoSmA99G6htrLBn11ax2bTQ== integrity sha512-gGEwIJCXV3/wpIJ/wRyhmieLOSAY7HeFFjb+wEfHs04aE63JYR+rXXV4b7rBpEh1ZgNV9U91zfet/iQG7J8HBQ==
dependencies: dependencies:
web-streams-polyfill "^3.3.2" 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" resolved "https://registry.yarnpkg.com/expo-media-library/-/expo-media-library-17.0.3.tgz#4ee3e6a8a2544887d910a72eaf2a15858b78cc0e"
integrity sha512-vo8AqWxv1C8+U8dA5W43qs8+3dgD3VZDvcCkZBQTBnGr/2Rs7x6nNQD5s7UfYyr6qmW6102JB3+OUKHpkwEssg== integrity sha512-vo8AqWxv1C8+U8dA5W43qs8+3dgD3VZDvcCkZBQTBnGr/2Rs7x6nNQD5s7UfYyr6qmW6102JB3+OUKHpkwEssg==
expo-modules-autolinking@2.0.3: expo-modules-autolinking@2.0.4:
version "2.0.3" version "2.0.4"
resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.3.tgz#c0de0129bedf1b6f9aa36093e435d00509f27fcd" resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.4.tgz#28fcd12fb0d066a2933cca3bf3b597da0f6b2f2a"
integrity sha512-Q/ALJ54eS7Cr7cmbP+unEDTkHFQivQerWWrqZxuXOrSFYGCYU22+/xAZXaJOpZwseOVsP74zSkoRY/wBimVs7w== integrity sha512-e0p+19NhmD50U7s7BV7kWIypWmTNC9n/VlJKlXS05hM/zX7pe6JKmXyb+BFnXJq3SLBalLCUY0tu2gEUF3XeVg==
dependencies: dependencies:
"@expo/spawn-async" "^1.7.2" "@expo/spawn-async" "^1.7.2"
chalk "^4.1.0" chalk "^4.1.0"
@@ -10556,7 +10554,14 @@ expo-modules-autolinking@2.0.3:
require-from-string "^2.0.2" require-from-string "^2.0.2"
resolve-from "^5.0.0" 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" version "2.1.1"
resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-2.1.1.tgz#970af4cfd70c8aa6fc0096dd0a6578aa003a479f" resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-2.1.1.tgz#970af4cfd70c8aa6fc0096dd0a6578aa003a479f"
integrity sha512-yQzYCLR2mre4BNMXuqkeJ0oSNgmGEMI6BcmIzeNZbC2NFEjiaDpKvlV9bclYCtyVhUEVNbJcEPYMr6c1Y4eR4w== 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" resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-13.0.0.tgz#fbc46f4afdaa265a2811fe88c2a589aae2d2de0f"
integrity sha512-b23ymicRmYn/Pjj05sl9tFZHN5cH9I1f0yiqY1Yk8Q3oCx0Aznri82DnTYA4T/J6D9vrkraX0wQ4jWVMOffmlg== integrity sha512-b23ymicRmYn/Pjj05sl9tFZHN5cH9I1f0yiqY1Yk8Q3oCx0Aznri82DnTYA4T/J6D9vrkraX0wQ4jWVMOffmlg==
expo-splash-screen@~0.29.16: expo-splash-screen@~0.29.18:
version "0.29.16" version "0.29.18"
resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.29.16.tgz#80b49af2605213e661a40022d724caa1ea48ccb3" resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.29.18.tgz#96ccce3d5a03389a9061743903b0a77c22a16796"
integrity sha512-1WnExDA23hEJhz+djUthVUWxUvVtDT9sqRrpCgU4srG2OfBN0NryJ+Fbnoc1V2xw2uYc4Ij3ru0nH9a1TNvW9w== integrity sha512-bTBY+LF6YtYen2j60yGNh2SX/tG4UXZAyBCMMriOSiZZ7LSCs3ARyEufaSiWk+ckWShTeMqItOnaAN/CAF8MJA==
dependencies: dependencies:
"@expo/prebuild-config" "^8.0.22" "@expo/prebuild-config" "^8.0.23"
expo-status-bar@~2.0.0: expo-status-bar@~2.0.0:
version "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" resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-14.0.1.tgz#97f3f141b0897364bc8364d90d6e29df0beec8aa"
integrity sha512-QM9F3ie+UyIOoBvqFmT6CZojb1vMc2H+7ZlMT5dEu1PL2jtYyOeK2hLfbt/EMt7CBm/w+P29H9W9Y9gdebOkuQ== integrity sha512-QM9F3ie+UyIOoBvqFmT6CZojb1vMc2H+7ZlMT5dEu1PL2jtYyOeK2hLfbt/EMt7CBm/w+P29H9W9Y9gdebOkuQ==
expo@~52.0.17: expo@~52.0.19:
version "52.0.17" version "52.0.19"
resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.17.tgz#8a3edc20dabdb69a47f7b4b92e1bb96284044c14" resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.19.tgz#1b881c96ea595da0c5f3c13f578bc368c5261f4a"
integrity sha512-f0WBD2T6p9r/a8v8MqkoWQq7TmbbAgPUg2zZtOp+kBrSCb3obHeNAsPDAUFzh+jEgug2qDVVkauBJa6ACe9AMg== integrity sha512-wOb/wbiQa0xqQRhgVBuOhLRus05TSw6fgThVMrPQgdLo24EPuT/ZAiRVcVRdjrEbwOqCDumgQCB7636B9J+jKg==
dependencies: dependencies:
"@babel/runtime" "^7.20.0" "@babel/runtime" "^7.20.0"
"@expo/cli" "0.22.3" "@expo/cli" "0.22.6"
"@expo/config" "~10.0.6" "@expo/config" "~10.0.6"
"@expo/config-plugins" "~9.0.12" "@expo/config-plugins" "~9.0.12"
"@expo/fingerprint" "0.11.3" "@expo/fingerprint" "0.11.4"
"@expo/metro-config" "0.19.6" "@expo/metro-config" "0.19.8"
"@expo/vector-icons" "^14.0.0" "@expo/vector-icons" "^14.0.0"
babel-preset-expo "~12.0.3" babel-preset-expo "~12.0.4"
expo-asset "~11.0.1" expo-asset "~11.0.1"
expo-constants "~17.0.3" expo-constants "~17.0.3"
expo-file-system "~18.0.4" expo-file-system "~18.0.6"
expo-font "~13.0.1" expo-font "~13.0.1"
expo-keep-awake "~14.0.1" expo-keep-awake "~14.0.1"
expo-modules-autolinking "2.0.3" expo-modules-autolinking "2.0.4"
expo-modules-core "2.1.1" expo-modules-core "2.1.2"
fbemitter "^3.0.0" fbemitter "^3.0.0"
web-streams-polyfill "^3.3.2" web-streams-polyfill "^3.3.2"
whatwg-url-without-unicode "8.0.0-3" whatwg-url-without-unicode "8.0.0-3"