···11diff --git a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
22-index b85291e..07a5d3c 100644
22+index b85291e..546709d 100644
33--- a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
44+++ b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
55@@ -78,13 +78,20 @@ public final class ExpoUpdatesUpdate: Update {
66 status = UpdateStatus.StatusPending
77 }
88-88+99+ // Instead of relying on various hacks to get the correct format for the specific
1010+ // platform on the backend, we can just add this little patch..
1111+ let dateFormatter = DateFormatter()
···11import React from 'react'
22-import {View} from 'react-native'
22+import {GestureResponderEvent, View} from 'react-native'
33import {msg} from '@lingui/macro'
44import {useLingui} from '@lingui/react'
5566import {atoms as a, useBreakpoints, useTheme} from '#/alf'
77-import {Button, ButtonColor, ButtonText} from '#/components/Button'
77+import {Button, ButtonColor, ButtonProps, ButtonText} from '#/components/Button'
88import * as Dialog from '#/components/Dialog'
99import {Text} from '#/components/Typography'
1010···136136 * Note: The dialog will close automatically when the action is pressed, you
137137 * should NOT close the dialog as a side effect of this method.
138138 */
139139- onPress: () => void
139139+ onPress: ButtonProps['onPress']
140140 color?: ButtonColor
141141 /**
142142 * Optional i18n string. If undefined, it will default to "Confirm".
···147147 const {_} = useLingui()
148148 const {gtMobile} = useBreakpoints()
149149 const {close} = Dialog.useDialogContext()
150150- const handleOnPress = React.useCallback(() => {
151151- close(onPress)
152152- }, [close, onPress])
150150+ const handleOnPress = React.useCallback(
151151+ (e: GestureResponderEvent) => {
152152+ close(() => onPress?.(e))
153153+ },
154154+ [close, onPress],
155155+ )
153156154157 return (
155158 <Button
···186189 * Note: The dialog will close automatically when the action is pressed, you
187190 * should NOT close the dialog as a side effect of this method.
188191 */
189189- onConfirm: () => void
192192+ onConfirm: ButtonProps['onPress']
190193 confirmButtonColor?: ButtonColor
191194 showCancel?: boolean
192195}>) {
···5566import {batchedUpdates} from '#/lib/batchedUpdates'
77import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search'
88+import {findAllProfilesInQueryData as findAllProfilesInKnownFollowersQueryData} from '../queries/known-followers'
89import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members'
910import {findAllProfilesInQueryData as findAllProfilesInListConvosQueryData} from '../queries/messages/list-converations'
1011import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '../queries/my-blocked-accounts'
···111112 yield* findAllProfilesInListConvosQueryData(queryClient, did)
112113 yield* findAllProfilesInFeedsQueryData(queryClient, did)
113114 yield* findAllProfilesInPostThreadQueryData(queryClient, did)
115115+ yield* findAllProfilesInKnownFollowersQueryData(queryClient, did)
114116}
+125-5
src/state/queries/feed.ts
···11+import {useCallback, useEffect, useMemo, useRef} from 'react'
12import {
23 AppBskyActorDefs,
34 AppBskyFeedDefs,
···171172 })
172173}
173174174174-export const useGetPopularFeedsQueryKey = ['getPopularFeeds']
175175+// HACK
176176+// the protocol doesn't yet tell us which feeds are personalized
177177+// this list is used to filter out feed recommendations from logged out users
178178+// for the ones we know need it
179179+// -prf
180180+export const KNOWN_AUTHED_ONLY_FEEDS = [
181181+ 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app
182182+ 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed
183183+ 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed
184184+ 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow
185185+ 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz
186186+ 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky
187187+ 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz
188188+ 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why
189189+]
190190+191191+type GetPopularFeedsOptions = {limit?: number}
175192176176-export function useGetPopularFeedsQuery() {
193193+export function createGetPopularFeedsQueryKey(
194194+ options?: GetPopularFeedsOptions,
195195+) {
196196+ return ['getPopularFeeds', options]
197197+}
198198+199199+export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
200200+ const {hasSession} = useSession()
177201 const agent = useAgent()
178178- return useInfiniteQuery<
202202+ const limit = options?.limit || 10
203203+ const {data: preferences} = usePreferencesQuery()
204204+205205+ // Make sure this doesn't invalidate unless really needed.
206206+ const selectArgs = useMemo(
207207+ () => ({
208208+ hasSession,
209209+ savedFeeds: preferences?.savedFeeds || [],
210210+ }),
211211+ [hasSession, preferences?.savedFeeds],
212212+ )
213213+ const lastPageCountRef = useRef(0)
214214+215215+ const query = useInfiniteQuery<
179216 AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema,
180217 Error,
181218 InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
182219 QueryKey,
183220 string | undefined
184221 >({
185185- queryKey: useGetPopularFeedsQueryKey,
222222+ queryKey: createGetPopularFeedsQueryKey(options),
186223 queryFn: async ({pageParam}) => {
187224 const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
188188- limit: 10,
225225+ limit,
189226 cursor: pageParam,
190227 })
191228 return res.data
192229 },
193230 initialPageParam: undefined,
194231 getNextPageParam: lastPage => lastPage.cursor,
232232+ select: useCallback(
233233+ (
234234+ data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
235235+ ) => {
236236+ const {savedFeeds, hasSession: hasSessionInner} = selectArgs
237237+ data?.pages.map(page => {
238238+ page.feeds = page.feeds.filter(feed => {
239239+ if (
240240+ !hasSessionInner &&
241241+ KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri)
242242+ ) {
243243+ return false
244244+ }
245245+ const alreadySaved = Boolean(
246246+ savedFeeds?.find(f => {
247247+ return f.value === feed.uri
248248+ }),
249249+ )
250250+ return !alreadySaved
251251+ })
252252+253253+ return page
254254+ })
255255+256256+ return data
257257+ },
258258+ [selectArgs /* Don't change. Everything needs to go into selectArgs. */],
259259+ ),
195260 })
261261+262262+ useEffect(() => {
263263+ const {isFetching, hasNextPage, data} = query
264264+ if (isFetching || !hasNextPage) {
265265+ return
266266+ }
267267+268268+ // avoid double-fires of fetchNextPage()
269269+ if (
270270+ lastPageCountRef.current !== 0 &&
271271+ lastPageCountRef.current === data?.pages?.length
272272+ ) {
273273+ return
274274+ }
275275+276276+ // fetch next page if we haven't gotten a full page of content
277277+ let count = 0
278278+ for (const page of data?.pages || []) {
279279+ count += page.feeds.length
280280+ }
281281+ if (count < limit && (data?.pages.length || 0) < 6) {
282282+ query.fetchNextPage()
283283+ lastPageCountRef.current = data?.pages?.length || 0
284284+ }
285285+ }, [query, limit])
286286+287287+ return query
196288}
197289198290export function useSearchPopularFeedsMutation() {
199291 const agent = useAgent()
200292 return useMutation({
201293 mutationFn: async (query: string) => {
294294+ const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
295295+ limit: 10,
296296+ query: query,
297297+ })
298298+299299+ return res.data.feeds
300300+ },
301301+ })
302302+}
303303+304304+const popularFeedsSearchQueryKeyRoot = 'popularFeedsSearch'
305305+export const createPopularFeedsSearchQueryKey = (query: string) => [
306306+ popularFeedsSearchQueryKeyRoot,
307307+ query,
308308+]
309309+310310+export function usePopularFeedsSearch({
311311+ query,
312312+ enabled,
313313+}: {
314314+ query: string
315315+ enabled?: boolean
316316+}) {
317317+ const agent = useAgent()
318318+ return useQuery({
319319+ enabled,
320320+ queryKey: createPopularFeedsSearchQueryKey(query),
321321+ queryFn: async () => {
202322 const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
203323 limit: 10,
204324 query: query,
+30-2
src/state/queries/known-followers.ts
···11-import {AppBskyGraphGetKnownFollowers} from '@atproto/api'
22-import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query'
11+import {AppBskyActorDefs, AppBskyGraphGetKnownFollowers} from '@atproto/api'
22+import {
33+ InfiniteData,
44+ QueryClient,
55+ QueryKey,
66+ useInfiniteQuery,
77+} from '@tanstack/react-query'
3849import {useAgent} from '#/state/session'
510···3237 enabled: !!did,
3338 })
3439}
4040+4141+export function* findAllProfilesInQueryData(
4242+ queryClient: QueryClient,
4343+ did: string,
4444+): Generator<AppBskyActorDefs.ProfileView, void> {
4545+ const queryDatas = queryClient.getQueriesData<
4646+ InfiniteData<AppBskyGraphGetKnownFollowers.OutputSchema>
4747+ >({
4848+ queryKey: [RQKEY_ROOT],
4949+ })
5050+ for (const [_queryKey, queryData] of queryDatas) {
5151+ if (!queryData?.pages) {
5252+ continue
5353+ }
5454+ for (const page of queryData?.pages) {
5555+ for (const follow of page.followers) {
5656+ if (follow.did === did) {
5757+ yield follow
5858+ }
5959+ }
6060+ }
6161+ }
6262+}