···4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
607import {logEvent} from '#/lib/statsig/statsig'
8import {isWeb} from '#/platform/detection'
9import {useModerationOpts} from '#/state/preferences/moderation-opts'
···13import {useSession} from '#/state/session'
14import {type Follow10ProgressGuide} from '#/state/shell/progress-guide'
15import {type ListMethods} from '#/view/com/util/List'
16-import {
17- popularInterests,
18- useInterestsDisplayNames,
19-} from '#/screens/Onboarding/state'
20import {
21 atoms as a,
22 native,
···4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
67+import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
8import {logEvent} from '#/lib/statsig/statsig'
9import {isWeb} from '#/platform/detection'
10import {useModerationOpts} from '#/state/preferences/moderation-opts'
···14import {useSession} from '#/state/session'
15import {type Follow10ProgressGuide} from '#/state/shell/progress-guide'
16import {type ListMethods} from '#/view/com/util/List'
000017import {
18 atoms as a,
19 native,
···1import React from 'react'
2import {type TextStyle, View, type ViewStyle} from 'react-native'
304import {capitalize} from '#/lib/strings/capitalize'
5-import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
6import {atoms as a, native, useTheme} from '#/alf'
7import * as Toggle from '#/components/forms/Toggle'
8import {Text} from '#/components/Typography'
910-export function InterestButton({interest}: {interest: string}) {
11 const t = useTheme()
12 const interestsDisplayNames = useInterestsDisplayNames()
13 const ctx = Toggle.useItemContext()
···1import React from 'react'
2import {type TextStyle, View, type ViewStyle} from 'react-native'
34+import {type Interest, useInterestsDisplayNames} from '#/lib/interests'
5import {capitalize} from '#/lib/strings/capitalize'
06import {atoms as a, native, useTheme} from '#/alf'
7import * as Toggle from '#/components/forms/Toggle'
8import {Text} from '#/components/Typography'
910+export function InterestButton({interest}: {interest: Interest}) {
11 const t = useTheme()
12 const interestsDisplayNames = useInterestsDisplayNames()
13 const ctx = Toggle.useItemContext()
···10import {useQueryClient} from '@tanstack/react-query'
11import * as bcp47Match from 'bcp-47-match'
1213+import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
14import {cleanError} from '#/lib/strings/errors'
15import {sanitizeHandle} from '#/lib/strings/handles'
16import {logger} from '#/logger'
···42import {List} from '#/view/com/util/List'
43import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
44import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
000045import {
46 StarterPackCard,
47 StarterPackCardSkeleton,
···929 <View style={[a.absolute, a.inset_0, t.atoms.bg, {top: -2}]} />
930 <ModuleHeader.FeedLink feed={item.feed}>
931 <ModuleHeader.FeedAvatar feed={item.feed} />
932+ <View style={[a.flex_1, a.gap_2xs]}>
933 <ModuleHeader.TitleText style={[a.text_lg]}>
934 {item.feed.displayName}
935 </ModuleHeader.TitleText>
···1079 windowSize={platform({android: 11})}
1080 /**
1081 * Default: 10
1082+ *
1083+ * NOTE: This was 1 on Android. Unfortunately this leads to the list totally freaking out
1084+ * when the sticky headers changed. I made a minimal reproduction and yeah, it's this prop.
1085+ * Totally fine when the sticky headers are static, but when they're dynamic, it's a mess.
1086+ *
1087+ * Repro: https://github.com/mozzius/stickyindices-repro
1088+ *
1089+ * I then found doubling this prop on iOS also reduced it freaking out there as well.
1090+ *
1091+ * Trades off seeing more blank space due to it having to render more items before it can show anything.
1092+ * -sfn
1093 */
1094+ maxToRenderPerBatch={platform({android: 10, ios: 20})}
1095 /**
1096 * Default: 50
1097+ *
1098+ * NOTE: This was 25 on Android. However, due to maxToRenderPerBatch being set to 10,
1099+ * the lower batching period is no longer necessary (?)
1100 */
1101+ updateCellsBatchingPeriod={50}
1102 refreshing={isPTR}
1103 onRefresh={onPTR}
1104 />
···3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
506import {Nux, useSaveNux} from '#/state/queries/nuxs'
7import {usePreferencesQuery} from '#/state/queries/preferences'
8-import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
9import {atoms as a, useTheme} from '#/alf'
10import {Button, ButtonIcon, ButtonText} from '#/components/Button'
11import {Shapes_Stroke2_Corner0_Rounded as Shapes} from '#/components/icons/Shapes'
···3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
56+import {useInterestsDisplayNames} from '#/lib/interests'
7import {Nux, useSaveNux} from '#/state/queries/nuxs'
8import {usePreferencesQuery} from '#/state/queries/preferences'
09import {atoms as a, useTheme} from '#/alf'
10import {Button, ButtonIcon, ButtonText} from '#/components/Button'
11import {Shapes_Stroke2_Corner0_Rounded as Shapes} from '#/components/icons/Shapes'
···5import {useLingui} from '@lingui/react'
6import {type InfiniteData} from '@tanstack/react-query'
708import {logger} from '#/logger'
9import {usePreferencesQuery} from '#/state/queries/preferences'
10import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
11-import {
12- popularInterests,
13- useInterestsDisplayNames,
14-} from '#/screens/Onboarding/state'
15import {useTheme} from '#/alf'
16import {atoms as a} from '#/alf'
17import {boostInterests, InterestTabs} from '#/components/InterestTabs'
···5import {useLingui} from '@lingui/react'
6import {type InfiniteData} from '@tanstack/react-query'
78+import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
9import {logger} from '#/logger'
10import {usePreferencesQuery} from '#/state/queries/preferences'
11import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
000012import {useTheme} from '#/alf'
13import {atoms as a} from '#/alf'
14import {boostInterests, InterestTabs} from '#/components/InterestTabs'
+1-1
src/screens/Search/util/useSuggestedUsers.ts
···1import {useMemo} from 'react'
203import {useActorSearchPaginated} from '#/state/queries/actor-search'
4import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery'
5-import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
67/**
8 * Conditional hook, used in case a user is a non-english speaker, in which
···1import {useMemo} from 'react'
23+import {useInterestsDisplayNames} from '#/lib/interests'
4import {useActorSearchPaginated} from '#/state/queries/actor-search'
5import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery'
067/**
8 * Conditional hook, used in case a user is a non-english speaker, in which
+7-29
src/screens/Settings/InterestsSettings.tsx
···6import {useQueryClient} from '@tanstack/react-query'
7import debounce from 'lodash.debounce'
8000009import {type CommonNavigatorParams} from '#/lib/routes/types'
10import {
11 preferencesQueryKey,
···17import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery'
18import {useAgent} from '#/state/session'
19import * as Toast from '#/view/com/util/Toast'
20-import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
21import {atoms as a, useGutters, useTheme} from '#/alf'
22import {Admonition} from '#/components/Admonition'
23import {Divider} from '#/components/Divider'
···160 onChange={onChangeInterests}
161 label={_(msg`Select your interests from the options below`)}>
162 <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}>
163- {INTERESTS.map(interest => {
164 const name = interestsDisplayNames[interest]
165 if (!name) return null
166 return (
···178 )
179}
180181-export function InterestButton({interest}: {interest: string}) {
182 const t = useTheme()
183 const interestsDisplayNames = useInterestsDisplayNames()
184 const ctx = Toggle.useItemContext()
···230 </View>
231 )
232}
233-234-const INTERESTS = [
235- 'animals',
236- 'art',
237- 'books',
238- 'comedy',
239- 'comics',
240- 'culture',
241- 'dev',
242- 'education',
243- 'food',
244- 'gaming',
245- 'journalism',
246- 'movies',
247- 'music',
248- 'nature',
249- 'news',
250- 'pets',
251- 'photography',
252- 'politics',
253- 'science',
254- 'sports',
255- 'tech',
256- 'tv',
257- 'writers',
258-]
···6import {useQueryClient} from '@tanstack/react-query'
7import debounce from 'lodash.debounce'
89+import {
10+ type Interest,
11+ interests as allInterests,
12+ useInterestsDisplayNames,
13+} from '#/lib/interests'
14import {type CommonNavigatorParams} from '#/lib/routes/types'
15import {
16 preferencesQueryKey,
···22import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery'
23import {useAgent} from '#/state/session'
24import * as Toast from '#/view/com/util/Toast'
025import {atoms as a, useGutters, useTheme} from '#/alf'
26import {Admonition} from '#/components/Admonition'
27import {Divider} from '#/components/Divider'
···164 onChange={onChangeInterests}
165 label={_(msg`Select your interests from the options below`)}>
166 <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}>
167+ {allInterests.map(interest => {
168 const name = interestsDisplayNames[interest]
169 if (!name) return null
170 return (
···182 )
183}
184185+export function InterestButton({interest}: {interest: Interest}) {
186 const t = useTheme()
187 const interestsDisplayNames = useInterestsDisplayNames()
188 const ctx = Toggle.useItemContext()
···234 </View>
235 )
236}
00000000000000000000000000
-34
src/screens/Signup/StepInfo/Policies.tsx
···4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
67-import {webLinks} from '#/lib/constants'
8-import {useGate} from '#/lib/statsig/statsig'
9import {atoms as a, useTheme} from '#/alf'
10import {Admonition} from '#/components/Admonition'
11import {InlineLinkText} from '#/components/Link'
12import {Text} from '#/components/Typography'
1314-function CommunityGuidelinesNotice({}: {}) {
15- const {_} = useLingui()
16- const gate = useGate()
17-18- if (gate('disable_onboarding_policy_update_notice')) return null
19-20- return (
21- <View style={[a.pt_xs]}>
22- <Admonition type="tip">
23- <Trans>
24- You also agree to{' '}
25- <InlineLinkText
26- label={_(msg`Bluesky's Community Guidelines`)}
27- to={webLinks.communityDeprecated}>
28- Bluesky’s Community Guidelines
29- </InlineLinkText>
30- . An{' '}
31- <InlineLinkText
32- label={_(msg`Bluesky's Updated Community Guidelines`)}
33- to={webLinks.community}>
34- updated version of our Community Guidelines
35- </InlineLinkText>{' '}
36- will take effect on October 15th.
37- </Trans>
38- </Admonition>
39- </View>
40- )
41-}
42-43export const Policies = ({
44 serviceDescription,
45 needsGuardian,
···67 This service has not provided terms of service or a privacy policy.
68 </Trans>
69 </Admonition>
70- <CommunityGuidelinesNotice />
71 </View>
72 )
73 }
···145 </Trans>
146 </Admonition>
147 ) : undefined}
148-149- <CommunityGuidelinesNotice />
150 </View>
151 )
152}
···4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6007import {atoms as a, useTheme} from '#/alf'
8import {Admonition} from '#/components/Admonition'
9import {InlineLinkText} from '#/components/Link'
10import {Text} from '#/components/Typography'
110000000000000000000000000000012export const Policies = ({
13 serviceDescription,
14 needsGuardian,
···36 This service has not provided terms of service or a privacy policy.
37 </Trans>
38 </Admonition>
039 </View>
40 )
41 }
···113 </Trans>
114 </Admonition>
115 ) : undefined}
00116 </View>
117 )
118}
···135 } else if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
136 if (parentMetadata) {
137 /*
138- * Set this value before incrementing the parent's repliesSeenCounter
00139 */
140- metadata!.replyIndex = parentMetadata.repliesIndexCounter
141- // Increment the parent's repliesIndexCounter
142- parentMetadata.repliesIndexCounter += 1
143 }
144145 const post = views.threadPost({
···193 storeTraversalMetadata(metadatas, childMetadata)
194 if (childParentMetadata) {
195 /*
196- * Set this value before incrementing the parent's repliesIndexCounter
00197 */
198 childMetadata!.replyIndex =
199- childParentMetadata.repliesIndexCounter
200- childParentMetadata.repliesIndexCounter += 1
201 }
202203 const childPost = views.threadPost({
···264 if (nextItem?.type === 'threadPost')
265 metadata.nextItemDepth = nextItem?.depth
266267- /*
268- * Item is the last "sibling" if we know for sure we're out of
269- * replies on the parent (even though this item itself may have its
270- * own reply branches).
271 */
272- const isLastSiblingByCounts =
273 metadata.replyIndex ===
274- metadata.parentMetadata.repliesIndexCounter - 1
275276 /*
277 * Item can also be the last "sibling" if we know we don't have a
···287 * Ok now we can set the last sibling state.
288 */
289 metadata.isLastSibling =
290- isLastSiblingByCounts || isImplicitlyLastSibling
291292 /*
293 * Item is the last "child" in a branch if there is no next item,
···135 } else if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) {
136 if (parentMetadata) {
137 /*
138+ * Set this value before incrementing the `repliesSeenCounter` later
139+ * on, since `repliesSeenCounter` is 1-indexed and `replyIndex` is
140+ * 0-indexed.
141 */
142+ metadata!.replyIndex = parentMetadata.repliesSeenCounter
00143 }
144145 const post = views.threadPost({
···193 storeTraversalMetadata(metadatas, childMetadata)
194 if (childParentMetadata) {
195 /*
196+ * Set this value before incrementing the
197+ * `repliesSeenCounter` later on, since `repliesSeenCounter`
198+ * is 1-indexed and `replyIndex` is 0-indexed.
199 */
200 childMetadata!.replyIndex =
201+ childParentMetadata.repliesSeenCounter
0202 }
203204 const childPost = views.threadPost({
···265 if (nextItem?.type === 'threadPost')
266 metadata.nextItemDepth = nextItem?.depth
267268+ /**
269+ * Item is also the last "sibling" if its index matches the total
270+ * number of replies we're actually able to render to the page.
0271 */
272+ const isLastSiblingDueToMissingReplies =
273 metadata.replyIndex ===
274+ metadata.parentMetadata.repliesSeenCounter - 1
275276 /*
277 * Item can also be the last "sibling" if we know we don't have a
···287 * Ok now we can set the last sibling state.
288 */
289 metadata.isLastSibling =
290+ isImplicitlyLastSibling || isLastSiblingDueToMissingReplies
291292 /*
293 * Item is the last "child" in a branch if there is no next item,
+14-13
src/state/queries/usePostThread/types.ts
···193 */
194 repliesUnhydrated: number
195 /**
196- * The number of replies that have been seen so far in the traversal.
197- * Excludes replies that are moderated in some way, since those are not
198- * "seen" on first load. Use `repliesIndexCounter` for the total number of
199- * replies that were hydrated in the response.
200 *
201- * After traversal, we can use this to calculate if we actually got all the
202- * replies we expected, or if some were blocked, etc.
000000203 */
204 repliesSeenCounter: number
205 /**
206- * The total number of replies to this post hydrated in this response. Used
207- * for populating the `replyIndex` of the post by referencing this value on
208- * the parent.
209- */
210- repliesIndexCounter: number
211- /**
212- * The index-0-based index of this reply in the parent post's replies.
213 */
214 replyIndex: number
215 /**
···193 */
194 repliesUnhydrated: number
195 /**
196+ * The number of replies that have been "seen" (actually able to be rendered)
197+ * so far in the traversal. Excludes replies that are moderated in some way,
198+ * since those are not "seen" on first load.
0199 *
200+ * We use this to compute the `replyIndex` values of the children of this
201+ * parent. E.g. if a reply is not hydrated on the response, or is moderated
202+ * in some way (including by the user), this value is not incremented. So
203+ * this represents the _actual_ index of the reply in the rendered view.
204+ *
205+ * Note: this is a "counter", not an "index". Because this value is
206+ * incremented starting from 0, it is 1-indexed. So to when comparing to the
207+ * `replyIndex`, you'll need to subtract 1 from this value.
208 */
209 repliesSeenCounter: number
210 /**
211+ * The index-0-based index of this reply in the parent post's replies. This
212+ * is computed from the `repliesSeenCounter` of the parent post, prior to it
213+ * being incremented for this reply.
0000214 */
215 replyIndex: number
216 /**