diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/modals/index.tsx | 8 | ||||
-rw-r--r-- | src/state/persisted/legacy.ts | 1 | ||||
-rw-r--r-- | src/state/persisted/schema.ts | 16 | ||||
-rw-r--r-- | src/state/preferences/external-embeds-prefs.tsx | 54 | ||||
-rw-r--r-- | src/state/preferences/index.tsx | 9 | ||||
-rw-r--r-- | src/state/queries/actor-autocomplete.ts | 2 | ||||
-rw-r--r-- | src/state/queries/app-passwords.ts | 1 | ||||
-rw-r--r-- | src/state/queries/invites.ts | 1 | ||||
-rw-r--r-- | src/state/queries/notifications/unread.tsx | 4 | ||||
-rw-r--r-- | src/state/queries/post-feed.ts | 4 | ||||
-rw-r--r-- | src/state/session/index.tsx | 78 | ||||
-rw-r--r-- | src/state/shell/composer.tsx | 1 |
12 files changed, 145 insertions, 34 deletions
diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 81a220d1b..8c32c472a 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -6,6 +6,7 @@ import {Image as RNImage} from 'react-native-image-crop-picker' import {ImageModel} from '#/state/models/media/image' import {GalleryModel} from '#/state/models/media/gallery' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' +import {EmbedPlayerSource} from '#/lib/strings/embed-player.ts' import {ThreadgateSetting} from '../queries/threadgate' export interface ConfirmModal { @@ -180,6 +181,12 @@ export interface LinkWarningModal { href: string } +export interface EmbedConsentModal { + name: 'embed-consent' + source: EmbedPlayerSource + onAccept: () => void +} + export type Modal = // Account | AddAppPasswordModal @@ -223,6 +230,7 @@ export type Modal = // Generic | ConfirmModal | LinkWarningModal + | EmbedConsentModal const ModalContext = React.createContext<{ isModalActive: boolean diff --git a/src/state/persisted/legacy.ts b/src/state/persisted/legacy.ts index cdb542f5a..334ef1d92 100644 --- a/src/state/persisted/legacy.ts +++ b/src/state/persisted/legacy.ts @@ -109,6 +109,7 @@ export function transform(legacy: Partial<LegacySchema>): Schema { step: legacy.onboarding?.step || defaults.onboarding.step, }, hiddenPosts: defaults.hiddenPosts, + externalEmbeds: defaults.externalEmbeds, } } diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 27b1f26bd..6a26cedae 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -1,6 +1,8 @@ import {z} from 'zod' import {deviceLocales} from '#/platform/detection' +const externalEmbedOptions = ['show', 'hide'] as const + // only data needed for rendering account page const accountSchema = z.object({ service: z.string(), @@ -30,6 +32,19 @@ export const schema = z.object({ appLanguage: z.string(), }), requireAltTextEnabled: z.boolean(), // should move to server + externalEmbeds: z + .object({ + giphy: z.enum(externalEmbedOptions).optional(), + tenor: z.enum(externalEmbedOptions).optional(), + youtube: z.enum(externalEmbedOptions).optional(), + youtubeShorts: z.enum(externalEmbedOptions).optional(), + twitch: z.enum(externalEmbedOptions).optional(), + vimeo: z.enum(externalEmbedOptions).optional(), + spotify: z.enum(externalEmbedOptions).optional(), + appleMusic: z.enum(externalEmbedOptions).optional(), + soundcloud: z.enum(externalEmbedOptions).optional(), + }) + .optional(), mutedThreads: z.array(z.string()), // should move to server invites: z.object({ copiedInvites: z.array(z.string()), @@ -60,6 +75,7 @@ export const defaults: Schema = { appLanguage: deviceLocales[0] || 'en', }, requireAltTextEnabled: false, + externalEmbeds: {}, mutedThreads: [], invites: { copiedInvites: [], diff --git a/src/state/preferences/external-embeds-prefs.tsx b/src/state/preferences/external-embeds-prefs.tsx new file mode 100644 index 000000000..0f6385fe8 --- /dev/null +++ b/src/state/preferences/external-embeds-prefs.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import * as persisted from '#/state/persisted' +import {EmbedPlayerSource} from 'lib/strings/embed-player' + +type StateContext = persisted.Schema['externalEmbeds'] +type SetContext = (source: EmbedPlayerSource, value: 'show' | 'hide') => void + +const stateContext = React.createContext<StateContext>( + persisted.defaults.externalEmbeds, +) +const setContext = React.createContext<SetContext>({} as SetContext) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState(persisted.get('externalEmbeds')) + + const setStateWrapped = React.useCallback( + (source: EmbedPlayerSource, value: 'show' | 'hide') => { + setState(prev => { + persisted.write('externalEmbeds', { + ...prev, + [source]: value, + }) + + return { + ...prev, + [source]: value, + } + }) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(persisted.get('externalEmbeds')) + }) + }, [setStateWrapped]) + + return ( + <stateContext.Provider value={state}> + <setContext.Provider value={setStateWrapped}> + {children} + </setContext.Provider> + </stateContext.Provider> + ) +} + +export function useExternalEmbedsPrefs() { + return React.useContext(stateContext) +} + +export function useSetExternalEmbedPref() { + return React.useContext(setContext) +} diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index 5ec659031..cc2d9244c 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -2,19 +2,26 @@ import React from 'react' import {Provider as LanguagesProvider} from './languages' import {Provider as AltTextRequiredProvider} from '../preferences/alt-text-required' import {Provider as HiddenPostsProvider} from '../preferences/hidden-posts' +import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs' export {useLanguagePrefs, useLanguagePrefsApi} from './languages' export { useRequireAltTextEnabled, useSetRequireAltTextEnabled, } from './alt-text-required' +export { + useExternalEmbedsPrefs, + useSetExternalEmbedPref, +} from './external-embeds-prefs' export * from './hidden-posts' export function Provider({children}: React.PropsWithChildren<{}>) { return ( <LanguagesProvider> <AltTextRequiredProvider> - <HiddenPostsProvider>{children}</HiddenPostsProvider> + <ExternalEmbedsProvider> + <HiddenPostsProvider>{children}</HiddenPostsProvider> + </ExternalEmbedsProvider> </AltTextRequiredProvider> </LanguagesProvider> ) diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts index 785e29765..ba9f97004 100644 --- a/src/state/queries/actor-autocomplete.ts +++ b/src/state/queries/actor-autocomplete.ts @@ -24,6 +24,8 @@ export function useActorAutocompleteQuery(prefix: string) { const {data: follows, isFetching} = useMyFollowsQuery() const moderationOpts = useModerationOpts() + prefix = prefix.toLowerCase() + return useQuery<AppBskyActorDefs.ProfileViewBasic[]>({ staleTime: STALE.MINUTES.ONE, queryKey: RQKEY(prefix || ''), diff --git a/src/state/queries/app-passwords.ts b/src/state/queries/app-passwords.ts index 4b9e09a8d..014244f01 100644 --- a/src/state/queries/app-passwords.ts +++ b/src/state/queries/app-passwords.ts @@ -9,7 +9,6 @@ export const RQKEY = () => ['app-passwords'] export function useAppPasswordsQuery() { return useQuery({ staleTime: STALE.MINUTES.FIVE, - refetchInterval: STALE.MINUTES.ONE, queryKey: RQKEY(), queryFn: async () => { const res = await getAgent().com.atproto.server.listAppPasswords({}) diff --git a/src/state/queries/invites.ts b/src/state/queries/invites.ts index bfea402e1..9ae9c707f 100644 --- a/src/state/queries/invites.ts +++ b/src/state/queries/invites.ts @@ -16,7 +16,6 @@ export type InviteCodesQueryResponse = Exclude< export function useInviteCodesQuery() { return useQuery({ staleTime: STALE.MINUTES.FIVE, - refetchInterval: STALE.MINUTES.FIVE, queryKey: ['inviteCodes'], queryFn: async () => { const res = await getAgent() diff --git a/src/state/queries/notifications/unread.tsx b/src/state/queries/notifications/unread.tsx index abaabbf0e..d604e8fe0 100644 --- a/src/state/queries/notifications/unread.tsx +++ b/src/state/queries/notifications/unread.tsx @@ -15,6 +15,7 @@ import {useMutedThreads} from '#/state/muted-threads' import {RQKEY as RQKEY_NOTIFS} from './feed' import {logger} from '#/logger' import {truncateAndInvalidate} from '../util' +import {AppState} from 'react-native' const UPDATE_INTERVAL = 30 * 1e3 // 30sec @@ -97,6 +98,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { async checkUnread({invalidate}: {invalidate?: boolean} = {}) { try { if (!getAgent().session) return + if (AppState.currentState !== 'active') { + return + } // count const page = await fetchPage({ diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 0e943622a..dbb729133 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -1,4 +1,5 @@ import React, {useCallback, useEffect, useRef} from 'react' +import {AppState} from 'react-native' import {AppBskyFeedDefs, AppBskyFeedPost, PostModeration} from '@atproto/api' import { useInfiniteQuery, @@ -312,6 +313,9 @@ export async function pollLatest(page: FeedPage | undefined) { if (!page) { return false } + if (AppState.currentState !== 'active') { + return + } logger.debug('usePostFeedQuery: pollLatest') const post = await page.api.peekLatest() diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index aa8c94ebc..17f25570e 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -102,10 +102,21 @@ function createPersistSessionHandler( expired: boolean refreshedAccount: SessionAccount }) => void, + { + networkErrorCallback, + }: { + networkErrorCallback?: () => void + } = {}, ): AtpPersistSessionHandler { return function persistSession(event, session) { const expired = event === 'expired' || event === 'create-failed' + if (event === 'network-error') { + logger.warn(`session: persistSessionHandler received network-error event`) + networkErrorCallback?.() + return + } + const refreshedAccount: SessionAccount = { service: account.service, did: session?.did || account.did, @@ -179,16 +190,26 @@ export function Provider({children}: React.PropsWithChildren<{}>) { [setStateAndPersist], ) + const clearCurrentAccount = React.useCallback(() => { + logger.debug( + `session: clear current account`, + {}, + logger.DebugContext.session, + ) + __globalAgent = PUBLIC_BSKY_AGENT + queryClient.clear() + setStateAndPersist(s => ({ + ...s, + currentAccount: undefined, + })) + }, [setStateAndPersist, queryClient]) + const createAccount = React.useCallback<ApiContext['createAccount']>( async ({service, email, password, handle, inviteCode}: any) => { - logger.debug( - `session: creating account`, - { - service, - handle, - }, - logger.DebugContext.session, - ) + logger.info(`session: creating account`, { + service, + handle, + }) track('Try Create Account') const agent = new BskyAgent({service}) @@ -215,9 +236,13 @@ export function Provider({children}: React.PropsWithChildren<{}>) { } agent.setPersistSessionHandler( - createPersistSessionHandler(account, ({expired, refreshedAccount}) => { - upsertAccount(refreshedAccount, expired) - }), + createPersistSessionHandler( + account, + ({expired, refreshedAccount}) => { + upsertAccount(refreshedAccount, expired) + }, + {networkErrorCallback: clearCurrentAccount}, + ), ) __globalAgent = agent @@ -234,7 +259,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { ) track('Create Account') }, - [upsertAccount, queryClient], + [upsertAccount, queryClient, clearCurrentAccount], ) const login = React.useCallback<ApiContext['login']>( @@ -267,9 +292,13 @@ export function Provider({children}: React.PropsWithChildren<{}>) { } agent.setPersistSessionHandler( - createPersistSessionHandler(account, ({expired, refreshedAccount}) => { - upsertAccount(refreshedAccount, expired) - }), + createPersistSessionHandler( + account, + ({expired, refreshedAccount}) => { + upsertAccount(refreshedAccount, expired) + }, + {networkErrorCallback: clearCurrentAccount}, + ), ) __globalAgent = agent @@ -287,23 +316,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { track('Sign In', {resumedSession: false}) }, - [upsertAccount, queryClient], + [upsertAccount, queryClient, clearCurrentAccount], ) - const clearCurrentAccount = React.useCallback(() => { - logger.debug( - `session: clear current account`, - {}, - logger.DebugContext.session, - ) - __globalAgent = PUBLIC_BSKY_AGENT - queryClient.clear() - setStateAndPersist(s => ({ - ...s, - currentAccount: undefined, - })) - }, [setStateAndPersist, queryClient]) - const logout = React.useCallback<ApiContext['logout']>(async () => { clearCurrentAccount() logger.debug(`session: logout`, {}, logger.DebugContext.session) @@ -337,6 +352,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { ({expired, refreshedAccount}) => { upsertAccount(refreshedAccount, expired) }, + {networkErrorCallback: clearCurrentAccount}, ), }) @@ -437,7 +453,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { } } }, - [upsertAccount, queryClient], + [upsertAccount, queryClient, clearCurrentAccount], ) const resumeSession = React.useCallback<ApiContext['resumeSession']>( diff --git a/src/state/shell/composer.tsx b/src/state/shell/composer.tsx index bdf5e4a7a..9cf8ef8de 100644 --- a/src/state/shell/composer.tsx +++ b/src/state/shell/composer.tsx @@ -30,6 +30,7 @@ export interface ComposerOpts { onPost?: () => void quote?: ComposerOptsQuote mention?: string // handle of user to mention + openPicker?: (pos: DOMRect | undefined) => void } type StateContext = ComposerOpts | undefined |