Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53f65fac32 | |||
| 2e2e554f07 | |||
| 8e8bcfbc75 | |||
| 9e36c4abb0 | |||
| 17b014f4ae | |||
| 57a72a1108 | |||
| 19031bc699 | |||
| f18b801979 | |||
| e56b7e00b5 | |||
| 698ba036dd |
+54
-52
@@ -12,19 +12,15 @@ import {
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {PersistQueryClientProvider} from '@tanstack/react-query-persist-client'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
|
||||
import {init as initPersistedState} from '#/state/persisted'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
|
||||
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
|
||||
import * as notifications from 'lib/notifications/notifications'
|
||||
import {
|
||||
asyncStoragePersister,
|
||||
dehydrateOptions,
|
||||
queryClient,
|
||||
} from 'lib/react-query'
|
||||
import {useNotificationsListener} from 'lib/notifications/notifications'
|
||||
import {QueryProvider} from 'lib/react-query'
|
||||
import {s} from 'lib/styles'
|
||||
import {ThemeProvider} from 'lib/ThemeContext'
|
||||
import {Provider as DialogStateProvider} from 'state/dialogs'
|
||||
@@ -59,11 +55,11 @@ function InnerApp() {
|
||||
const {resumeSession} = useSessionApi()
|
||||
const theme = useColorModeTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
useIntentHandler()
|
||||
|
||||
// init
|
||||
useEffect(() => {
|
||||
notifications.init(queryClient)
|
||||
listenSessionDropped(() => {
|
||||
Toast.show(_(msg`Sorry! Your session expired. Please log in again.`))
|
||||
})
|
||||
@@ -79,25 +75,29 @@ function InnerApp() {
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<StatsigProvider>
|
||||
<LabelDefsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* All components should be within this provider */}
|
||||
<RootSiblingParent>
|
||||
<GestureHandlerRootView style={s.h100pct}>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
</GestureHandlerRootView>
|
||||
</RootSiblingParent>
|
||||
</ThemeProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</LabelDefsProvider>
|
||||
</StatsigProvider>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PushNotificationsListener>
|
||||
<StatsigProvider>
|
||||
<LabelDefsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* All components should be within this provider */}
|
||||
<RootSiblingParent>
|
||||
<GestureHandlerRootView style={s.h100pct}>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
</GestureHandlerRootView>
|
||||
</RootSiblingParent>
|
||||
</ThemeProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</LabelDefsProvider>
|
||||
</StatsigProvider>
|
||||
</PushNotificationsListener>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</Splash>
|
||||
</Alf>
|
||||
@@ -105,6 +105,12 @@ function InnerApp() {
|
||||
)
|
||||
}
|
||||
|
||||
function PushNotificationsListener({children}: {children: React.ReactNode}) {
|
||||
const queryClient = useQueryClient()
|
||||
useNotificationsListener(queryClient)
|
||||
return children
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [isReady, setReady] = useState(false)
|
||||
|
||||
@@ -121,31 +127,27 @@ function App() {
|
||||
* that is set up in the InnerApp component above.
|
||||
*/
|
||||
return (
|
||||
<PersistQueryClientProvider
|
||||
client={queryClient}
|
||||
persistOptions={{persister: asyncStoragePersister, dehydrateOptions}}>
|
||||
<SessionProvider>
|
||||
<ShellStateProvider>
|
||||
<PrefsStateProvider>
|
||||
<MutedThreadsProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<I18nProvider>
|
||||
<PortalProvider>
|
||||
<InnerApp />
|
||||
</PortalProvider>
|
||||
</I18nProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
</MutedThreadsProvider>
|
||||
</PrefsStateProvider>
|
||||
</ShellStateProvider>
|
||||
</SessionProvider>
|
||||
</PersistQueryClientProvider>
|
||||
<SessionProvider>
|
||||
<ShellStateProvider>
|
||||
<PrefsStateProvider>
|
||||
<MutedThreadsProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<I18nProvider>
|
||||
<PortalProvider>
|
||||
<InnerApp />
|
||||
</PortalProvider>
|
||||
</I18nProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
</MutedThreadsProvider>
|
||||
</PrefsStateProvider>
|
||||
</ShellStateProvider>
|
||||
</SessionProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+66
-74
@@ -1,44 +1,38 @@
|
||||
import 'lib/sentry' // must be near top
|
||||
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {PersistQueryClientProvider} from '@tanstack/react-query-persist-client'
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
import {RootSiblingParent} from 'react-native-root-siblings'
|
||||
|
||||
import 'view/icons'
|
||||
|
||||
import {ThemeProvider as Alf} from '#/alf'
|
||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {RootSiblingParent} from 'react-native-root-siblings'
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
|
||||
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
|
||||
import {init as initPersistedState} from '#/state/persisted'
|
||||
import {Shell} from 'view/shell/index'
|
||||
import {ToastContainer} from 'view/com/util/Toast.web'
|
||||
import {ThemeProvider} from 'lib/ThemeContext'
|
||||
import {
|
||||
queryClient,
|
||||
asyncStoragePersister,
|
||||
dehydrateOptions,
|
||||
} from 'lib/react-query'
|
||||
import {Provider as ShellStateProvider} from 'state/shell'
|
||||
import {Provider as ModalStateProvider} from 'state/modals'
|
||||
import {Provider as DialogStateProvider} from 'state/dialogs'
|
||||
import {Provider as LightboxStateProvider} from 'state/lightbox'
|
||||
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||
import {Provider as PrefsStateProvider} from 'state/preferences'
|
||||
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
|
||||
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
|
||||
import I18nProvider from './locale/i18nProvider'
|
||||
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
|
||||
import {QueryProvider} from 'lib/react-query'
|
||||
import {ThemeProvider} from 'lib/ThemeContext'
|
||||
import {Provider as DialogStateProvider} from 'state/dialogs'
|
||||
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||
import {Provider as LightboxStateProvider} from 'state/lightbox'
|
||||
import {Provider as ModalStateProvider} from 'state/modals'
|
||||
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||
import {Provider as PrefsStateProvider} from 'state/preferences'
|
||||
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
|
||||
import {
|
||||
Provider as SessionProvider,
|
||||
useSession,
|
||||
useSessionApi,
|
||||
} from 'state/session'
|
||||
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {Provider as ShellStateProvider} from 'state/shell'
|
||||
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
|
||||
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
|
||||
import {ToastContainer} from 'view/com/util/Toast.web'
|
||||
import {Shell} from 'view/shell/index'
|
||||
import {ThemeProvider as Alf} from '#/alf'
|
||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||
import {Provider as PortalProvider} from '#/components/Portal'
|
||||
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
|
||||
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
|
||||
import I18nProvider from './locale/i18nProvider'
|
||||
|
||||
function InnerApp() {
|
||||
const {isInitialLoad, currentAccount} = useSession()
|
||||
@@ -60,25 +54,27 @@ function InnerApp() {
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<StatsigProvider>
|
||||
<LabelDefsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* All components should be within this provider */}
|
||||
<RootSiblingParent>
|
||||
<SafeAreaProvider>
|
||||
<Shell />
|
||||
</SafeAreaProvider>
|
||||
</RootSiblingParent>
|
||||
<ToastContainer />
|
||||
</ThemeProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</LabelDefsProvider>
|
||||
</StatsigProvider>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<StatsigProvider>
|
||||
<LabelDefsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* All components should be within this provider */}
|
||||
<RootSiblingParent>
|
||||
<SafeAreaProvider>
|
||||
<Shell />
|
||||
</SafeAreaProvider>
|
||||
</RootSiblingParent>
|
||||
<ToastContainer />
|
||||
</ThemeProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</LabelDefsProvider>
|
||||
</StatsigProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</Alf>
|
||||
)
|
||||
@@ -100,31 +96,27 @@ function App() {
|
||||
* that is set up in the InnerApp component above.
|
||||
*/
|
||||
return (
|
||||
<PersistQueryClientProvider
|
||||
client={queryClient}
|
||||
persistOptions={{persister: asyncStoragePersister, dehydrateOptions}}>
|
||||
<SessionProvider>
|
||||
<ShellStateProvider>
|
||||
<PrefsStateProvider>
|
||||
<MutedThreadsProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<I18nProvider>
|
||||
<PortalProvider>
|
||||
<InnerApp />
|
||||
</PortalProvider>
|
||||
</I18nProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
</MutedThreadsProvider>
|
||||
</PrefsStateProvider>
|
||||
</ShellStateProvider>
|
||||
</SessionProvider>
|
||||
</PersistQueryClientProvider>
|
||||
<SessionProvider>
|
||||
<ShellStateProvider>
|
||||
<PrefsStateProvider>
|
||||
<MutedThreadsProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<I18nProvider>
|
||||
<PortalProvider>
|
||||
<InnerApp />
|
||||
</PortalProvider>
|
||||
</I18nProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
</MutedThreadsProvider>
|
||||
</PrefsStateProvider>
|
||||
</ShellStateProvider>
|
||||
</SessionProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {useEffect} from 'react'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import {QueryClient} from '@tanstack/react-query'
|
||||
import {resetToTab} from '../../Navigation'
|
||||
import {devicePlatform, isIOS} from 'platform/detection'
|
||||
import {track} from 'lib/analytics/analytics'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {SessionAccount, getAgent} from '#/state/session'
|
||||
import {getAgent, SessionAccount} from '#/state/session'
|
||||
import {track} from 'lib/analytics/analytics'
|
||||
import {devicePlatform, isIOS} from 'platform/detection'
|
||||
import {resetToTab} from '../../Navigation'
|
||||
import {logEvent} from '../statsig/statsig'
|
||||
|
||||
const SERVICE_DID = (serviceUrl?: string) =>
|
||||
@@ -80,53 +82,63 @@ export function registerTokenChangeHandler(
|
||||
}
|
||||
}
|
||||
|
||||
export function init(queryClient: QueryClient) {
|
||||
// handle notifications that are received, both in the foreground or background
|
||||
// NOTE: currently just here for debug logging
|
||||
Notifications.addNotificationReceivedListener(event => {
|
||||
logger.debug(
|
||||
'Notifications: received',
|
||||
{event},
|
||||
logger.DebugContext.notifications,
|
||||
)
|
||||
if (event.request.trigger.type === 'push') {
|
||||
// handle payload-based deeplinks
|
||||
let payload
|
||||
if (isIOS) {
|
||||
payload = event.request.trigger.payload
|
||||
} else {
|
||||
// TODO: handle android payload deeplink
|
||||
}
|
||||
if (payload) {
|
||||
logger.debug(
|
||||
'Notifications: received payload',
|
||||
payload,
|
||||
logger.DebugContext.notifications,
|
||||
)
|
||||
// TODO: deeplink notif here
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// handle notifications that are tapped on
|
||||
Notifications.addNotificationResponseReceivedListener(response => {
|
||||
logger.debug(
|
||||
'Notifications: response received',
|
||||
{
|
||||
actionIdentifier: response.actionIdentifier,
|
||||
},
|
||||
logger.DebugContext.notifications,
|
||||
)
|
||||
if (response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER) {
|
||||
export function useNotificationsListener(queryClient: QueryClient) {
|
||||
useEffect(() => {
|
||||
// handle notifications that are received, both in the foreground or background
|
||||
// NOTE: currently just here for debug logging
|
||||
const sub1 = Notifications.addNotificationReceivedListener(event => {
|
||||
logger.debug(
|
||||
'User pressed a notification, opening notifications tab',
|
||||
{},
|
||||
'Notifications: received',
|
||||
{event},
|
||||
logger.DebugContext.notifications,
|
||||
)
|
||||
track('Notificatons:OpenApp')
|
||||
logEvent('notifications:openApp', {})
|
||||
truncateAndInvalidate(queryClient, RQKEY_NOTIFS())
|
||||
resetToTab('NotificationsTab') // open notifications tab
|
||||
if (event.request.trigger.type === 'push') {
|
||||
// handle payload-based deeplinks
|
||||
let payload
|
||||
if (isIOS) {
|
||||
payload = event.request.trigger.payload
|
||||
} else {
|
||||
// TODO: handle android payload deeplink
|
||||
}
|
||||
if (payload) {
|
||||
logger.debug(
|
||||
'Notifications: received payload',
|
||||
payload,
|
||||
logger.DebugContext.notifications,
|
||||
)
|
||||
// TODO: deeplink notif here
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// handle notifications that are tapped on
|
||||
const sub2 = Notifications.addNotificationResponseReceivedListener(
|
||||
response => {
|
||||
logger.debug(
|
||||
'Notifications: response received',
|
||||
{
|
||||
actionIdentifier: response.actionIdentifier,
|
||||
},
|
||||
logger.DebugContext.notifications,
|
||||
)
|
||||
if (
|
||||
response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER
|
||||
) {
|
||||
logger.debug(
|
||||
'User pressed a notification, opening notifications tab',
|
||||
{},
|
||||
logger.DebugContext.notifications,
|
||||
)
|
||||
track('Notificatons:OpenApp')
|
||||
logEvent('notifications:openApp', {})
|
||||
truncateAndInvalidate(queryClient, RQKEY_NOTIFS())
|
||||
resetToTab('NotificationsTab') // open notifications tab
|
||||
}
|
||||
},
|
||||
)
|
||||
return () => {
|
||||
sub1.remove()
|
||||
sub2.remove()
|
||||
}
|
||||
})
|
||||
}, [queryClient])
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import {AppState, AppStateStatus} from 'react-native'
|
||||
import {QueryClient, focusManager} from '@tanstack/react-query'
|
||||
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import {PersistQueryClientProviderProps} from '@tanstack/react-query-persist-client'
|
||||
|
||||
import {isNative} from '#/platform/detection'
|
||||
|
||||
// any query keys in this array will be persisted to AsyncStorage
|
||||
const STORED_CACHE_QUERY_KEYS = ['labelers-detailed-info']
|
||||
|
||||
focusManager.setEventListener(onFocus => {
|
||||
if (isNative) {
|
||||
const subscription = AppState.addEventListener(
|
||||
'change',
|
||||
(status: AppStateStatus) => {
|
||||
focusManager.setFocused(status === 'active')
|
||||
},
|
||||
)
|
||||
|
||||
return () => subscription.remove()
|
||||
} else if (typeof window !== 'undefined' && window.addEventListener) {
|
||||
// these handlers are a bit redundant but focus catches when the browser window
|
||||
// is blurred/focused while visibilitychange seems to only handle when the
|
||||
// window minimizes (both of them catch tab changes)
|
||||
// there's no harm to redundant fires because refetchOnWindowFocus is only
|
||||
// used with queries that employ stale data times
|
||||
const handler = () => onFocus()
|
||||
window.addEventListener('focus', handler, false)
|
||||
window.addEventListener('visibilitychange', handler, false)
|
||||
return () => {
|
||||
window.removeEventListener('visibilitychange', handler)
|
||||
window.removeEventListener('focus', handler)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
// NOTE
|
||||
// refetchOnWindowFocus breaks some UIs (like feeds)
|
||||
// so we only selectively want to enable this
|
||||
// -prf
|
||||
refetchOnWindowFocus: false,
|
||||
// Structural sharing between responses makes it impossible to rely on
|
||||
// "first seen" timestamps on objects to determine if they're fresh.
|
||||
// Disable this optimization so that we can rely on "first seen" timestamps.
|
||||
structuralSharing: false,
|
||||
// We don't want to retry queries by default, because in most cases we
|
||||
// want to fail early and show a response to the user. There are
|
||||
// exceptions, and those can be made on a per-query basis. For others, we
|
||||
// should give users controls to retry.
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const asyncStoragePersister = createAsyncStoragePersister({
|
||||
storage: AsyncStorage,
|
||||
key: 'queryCache',
|
||||
})
|
||||
|
||||
export const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehydrateOptions'] =
|
||||
{
|
||||
shouldDehydrateMutation: (_: any) => false,
|
||||
shouldDehydrateQuery: query => {
|
||||
return STORED_CACHE_QUERY_KEYS.includes(String(query.queryKey[0]))
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import React, {useRef, useState} from 'react'
|
||||
import {AppState, AppStateStatus} from 'react-native'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
|
||||
import {focusManager, QueryClient} from '@tanstack/react-query'
|
||||
import {
|
||||
PersistQueryClientProvider,
|
||||
PersistQueryClientProviderProps,
|
||||
} from '@tanstack/react-query-persist-client'
|
||||
|
||||
import {isNative} from '#/platform/detection'
|
||||
|
||||
// any query keys in this array will be persisted to AsyncStorage
|
||||
const STORED_CACHE_QUERY_KEYS = ['labelers-detailed-info']
|
||||
|
||||
focusManager.setEventListener(onFocus => {
|
||||
if (isNative) {
|
||||
const subscription = AppState.addEventListener(
|
||||
'change',
|
||||
(status: AppStateStatus) => {
|
||||
focusManager.setFocused(status === 'active')
|
||||
},
|
||||
)
|
||||
|
||||
return () => subscription.remove()
|
||||
} else if (typeof window !== 'undefined' && window.addEventListener) {
|
||||
// these handlers are a bit redundant but focus catches when the browser window
|
||||
// is blurred/focused while visibilitychange seems to only handle when the
|
||||
// window minimizes (both of them catch tab changes)
|
||||
// there's no harm to redundant fires because refetchOnWindowFocus is only
|
||||
// used with queries that employ stale data times
|
||||
const handler = () => onFocus()
|
||||
window.addEventListener('focus', handler, false)
|
||||
window.addEventListener('visibilitychange', handler, false)
|
||||
return () => {
|
||||
window.removeEventListener('visibilitychange', handler)
|
||||
window.removeEventListener('focus', handler)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const createQueryClient = () =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
// NOTE
|
||||
// refetchOnWindowFocus breaks some UIs (like feeds)
|
||||
// so we only selectively want to enable this
|
||||
// -prf
|
||||
refetchOnWindowFocus: false,
|
||||
// Structural sharing between responses makes it impossible to rely on
|
||||
// "first seen" timestamps on objects to determine if they're fresh.
|
||||
// Disable this optimization so that we can rely on "first seen" timestamps.
|
||||
structuralSharing: false,
|
||||
// We don't want to retry queries by default, because in most cases we
|
||||
// want to fail early and show a response to the user. There are
|
||||
// exceptions, and those can be made on a per-query basis. For others, we
|
||||
// should give users controls to retry.
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehydrateOptions'] =
|
||||
{
|
||||
shouldDehydrateMutation: (_: any) => false,
|
||||
shouldDehydrateQuery: query => {
|
||||
return STORED_CACHE_QUERY_KEYS.includes(String(query.queryKey[0]))
|
||||
},
|
||||
}
|
||||
|
||||
export function QueryProvider({
|
||||
children,
|
||||
currentDid,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
currentDid: string | undefined
|
||||
}) {
|
||||
return (
|
||||
<QueryProviderInner
|
||||
// Enforce we never reuse cache between users.
|
||||
// These two props MUST stay in sync.
|
||||
key={currentDid}
|
||||
currentDid={currentDid}>
|
||||
{children}
|
||||
</QueryProviderInner>
|
||||
)
|
||||
}
|
||||
|
||||
function QueryProviderInner({
|
||||
children,
|
||||
currentDid,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
currentDid: string | undefined
|
||||
}) {
|
||||
const initialDid = useRef(currentDid)
|
||||
if (currentDid !== initialDid.current) {
|
||||
throw Error(
|
||||
'Something is very wrong. Expected did to be stable due to key above.',
|
||||
)
|
||||
}
|
||||
// We create the query client here so that it's scoped to a specific DID.
|
||||
// Do not move the query client creation outside of this component.
|
||||
const [queryClient, _setQueryClient] = useState(() => createQueryClient())
|
||||
const [persistOptions, _setPersistOptions] = useState(() => {
|
||||
const asyncPersister = createAsyncStoragePersister({
|
||||
storage: AsyncStorage,
|
||||
key: 'queryClient-' + (currentDid ?? 'logged-out'),
|
||||
})
|
||||
return {
|
||||
persister: asyncPersister,
|
||||
dehydrateOptions,
|
||||
}
|
||||
})
|
||||
console.log('QUERY', currentDid)
|
||||
return (
|
||||
<PersistQueryClientProvider
|
||||
client={queryClient}
|
||||
persistOptions={persistOptions}>
|
||||
{children}
|
||||
</PersistQueryClientProvider>
|
||||
)
|
||||
}
|
||||
Vendored
+12
-6
@@ -1,13 +1,14 @@
|
||||
import {useEffect, useState, useMemo} from 'react'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
import {useEffect, useMemo, useState} from 'react'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {QueryClient} from '@tanstack/react-query'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
|
||||
import {batchedUpdates} from '#/lib/batchedUpdates'
|
||||
import {Shadow, castAsShadow} from './types'
|
||||
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '../queries/notifications/feed'
|
||||
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '../queries/post-feed'
|
||||
import {findAllPostsInQueryData as findAllPostsInThreadQueryData} from '../queries/post-thread'
|
||||
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '../queries/search-posts'
|
||||
import {queryClient} from 'lib/react-query'
|
||||
import {castAsShadow, Shadow} from './types'
|
||||
export type {Shadow} from './types'
|
||||
|
||||
export interface PostShadow {
|
||||
@@ -93,8 +94,12 @@ function mergeShadow(
|
||||
})
|
||||
}
|
||||
|
||||
export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
|
||||
const cachedPosts = findPostsInCache(uri)
|
||||
export function updatePostShadow(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
value: Partial<PostShadow>,
|
||||
) {
|
||||
const cachedPosts = findPostsInCache(queryClient, uri)
|
||||
for (let post of cachedPosts) {
|
||||
shadows.set(post, {...shadows.get(post), ...value})
|
||||
}
|
||||
@@ -104,6 +109,7 @@ export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
|
||||
}
|
||||
|
||||
function* findPostsInCache(
|
||||
queryClient: QueryClient,
|
||||
uri: string,
|
||||
): Generator<AppBskyFeedDefs.PostView, void> {
|
||||
for (let post of findAllPostsInFeedQueryData(queryClient, uri)) {
|
||||
|
||||
Vendored
+9
-6
@@ -1,7 +1,10 @@
|
||||
import {useEffect, useState, useMemo} from 'react'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
import {useEffect, useMemo, useState} from 'react'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {QueryClient} from '@tanstack/react-query'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
|
||||
import {batchedUpdates} from '#/lib/batchedUpdates'
|
||||
import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search'
|
||||
import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members'
|
||||
import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '../queries/my-blocked-accounts'
|
||||
import {findAllProfilesInQueryData as findAllProfilesInMyMutedAccountsQueryData} from '../queries/my-muted-accounts'
|
||||
@@ -11,9 +14,7 @@ import {findAllProfilesInQueryData as findAllProfilesInProfileQueryData} from '.
|
||||
import {findAllProfilesInQueryData as findAllProfilesInProfileFollowersQueryData} from '../queries/profile-followers'
|
||||
import {findAllProfilesInQueryData as findAllProfilesInProfileFollowsQueryData} from '../queries/profile-follows'
|
||||
import {findAllProfilesInQueryData as findAllProfilesInSuggestedFollowsQueryData} from '../queries/suggested-follows'
|
||||
import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search'
|
||||
import {Shadow, castAsShadow} from './types'
|
||||
import {queryClient} from 'lib/react-query'
|
||||
import {castAsShadow, Shadow} from './types'
|
||||
export type {Shadow} from './types'
|
||||
|
||||
export interface ProfileShadow {
|
||||
@@ -58,10 +59,11 @@ export function useProfileShadow<
|
||||
}
|
||||
|
||||
export function updateProfileShadow(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
value: Partial<ProfileShadow>,
|
||||
) {
|
||||
const cachedProfiles = findProfilesInCache(did)
|
||||
const cachedProfiles = findProfilesInCache(queryClient, did)
|
||||
for (let post of cachedProfiles) {
|
||||
shadows.set(post, {...shadows.get(post), ...value})
|
||||
}
|
||||
@@ -90,6 +92,7 @@ function mergeShadow<TProfileView extends AppBskyActorDefs.ProfileView>(
|
||||
}
|
||||
|
||||
function* findProfilesInCache(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
): Generator<AppBskyActorDefs.ProfileView, void> {
|
||||
yield* findAllProfilesInListMembersQueryData(queryClient, did)
|
||||
|
||||
@@ -3,37 +3,37 @@ import {AppState} from 'react-native'
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
ModerationDecision,
|
||||
AtUri,
|
||||
ModerationDecision,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
InfiniteData,
|
||||
QueryKey,
|
||||
QueryClient,
|
||||
QueryKey,
|
||||
useInfiniteQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {useFeedTuners} from '../preferences/feed-tuners'
|
||||
import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip'
|
||||
import {FeedAPI, ReasonFeedSource} from 'lib/api/feed/types'
|
||||
import {FollowingFeedAPI} from 'lib/api/feed/following'
|
||||
import {AuthorFeedAPI} from 'lib/api/feed/author'
|
||||
import {LikesFeedAPI} from 'lib/api/feed/likes'
|
||||
import {CustomFeedAPI} from 'lib/api/feed/custom'
|
||||
import {ListFeedAPI} from 'lib/api/feed/list'
|
||||
import {MergeFeedAPI} from 'lib/api/feed/merge'
|
||||
|
||||
import {HomeFeedAPI} from '#/lib/api/feed/home'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {precacheFeedPostProfiles} from './profile'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
|
||||
import {KnownError} from '#/view/com/posts/FeedErrorMessage'
|
||||
import {embedViewRecordToPostView, getEmbeddedPost} from './util'
|
||||
import {useModerationOpts} from './preferences'
|
||||
import {queryClient} from 'lib/react-query'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {AuthorFeedAPI} from 'lib/api/feed/author'
|
||||
import {CustomFeedAPI} from 'lib/api/feed/custom'
|
||||
import {FollowingFeedAPI} from 'lib/api/feed/following'
|
||||
import {LikesFeedAPI} from 'lib/api/feed/likes'
|
||||
import {ListFeedAPI} from 'lib/api/feed/list'
|
||||
import {MergeFeedAPI} from 'lib/api/feed/merge'
|
||||
import {FeedAPI, ReasonFeedSource} from 'lib/api/feed/types'
|
||||
import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip'
|
||||
import {BSKY_FEED_OWNER_DIDS} from 'lib/constants'
|
||||
import {KnownError} from '#/view/com/posts/FeedErrorMessage'
|
||||
import {useFeedTuners} from '../preferences/feed-tuners'
|
||||
import {useModerationOpts} from './preferences'
|
||||
import {precacheFeedPostProfiles} from './profile'
|
||||
import {embedViewRecordToPostView, getEmbeddedPost} from './util'
|
||||
|
||||
type ActorDid = string
|
||||
type AuthorFilter =
|
||||
@@ -458,7 +458,11 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function resetProfilePostsQueries(did: string, timeout = 0) {
|
||||
export function resetProfilePostsQueries(
|
||||
queryClient: QueryClient,
|
||||
did: string,
|
||||
timeout = 0,
|
||||
) {
|
||||
setTimeout(() => {
|
||||
queryClient.resetQueries({
|
||||
predicate: query =>
|
||||
|
||||
+20
-16
@@ -1,12 +1,13 @@
|
||||
import {useCallback} from 'react'
|
||||
import {AppBskyFeedDefs, AtUri} from '@atproto/api'
|
||||
import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
|
||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {getAgent} from '#/state/session'
|
||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
|
||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||
|
||||
export const RQKEY = (postUri: string) => ['post', postUri]
|
||||
|
||||
@@ -62,6 +63,7 @@ export function usePostLikeMutationQueue(
|
||||
logContext: LogEvents['post:like']['logContext'] &
|
||||
LogEvents['post:unlike']['logContext'],
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const postUri = post.uri
|
||||
const postCid = post.cid
|
||||
const initialLikeUri = post.viewer?.like
|
||||
@@ -89,7 +91,7 @@ export function usePostLikeMutationQueue(
|
||||
},
|
||||
onSuccess(finalLikeUri) {
|
||||
// finalize
|
||||
updatePostShadow(postUri, {
|
||||
updatePostShadow(queryClient, postUri, {
|
||||
likeUri: finalLikeUri,
|
||||
})
|
||||
},
|
||||
@@ -97,19 +99,19 @@ export function usePostLikeMutationQueue(
|
||||
|
||||
const queueLike = useCallback(() => {
|
||||
// optimistically update
|
||||
updatePostShadow(postUri, {
|
||||
updatePostShadow(queryClient, postUri, {
|
||||
likeUri: 'pending',
|
||||
})
|
||||
return queueToggle(true)
|
||||
}, [postUri, queueToggle])
|
||||
}, [queryClient, postUri, queueToggle])
|
||||
|
||||
const queueUnlike = useCallback(() => {
|
||||
// optimistically update
|
||||
updatePostShadow(postUri, {
|
||||
updatePostShadow(queryClient, postUri, {
|
||||
likeUri: undefined,
|
||||
})
|
||||
return queueToggle(false)
|
||||
}, [postUri, queueToggle])
|
||||
}, [queryClient, postUri, queueToggle])
|
||||
|
||||
return [queueLike, queueUnlike]
|
||||
}
|
||||
@@ -149,6 +151,7 @@ export function usePostRepostMutationQueue(
|
||||
logContext: LogEvents['post:repost']['logContext'] &
|
||||
LogEvents['post:unrepost']['logContext'],
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const postUri = post.uri
|
||||
const postCid = post.cid
|
||||
const initialRepostUri = post.viewer?.repost
|
||||
@@ -176,7 +179,7 @@ export function usePostRepostMutationQueue(
|
||||
},
|
||||
onSuccess(finalRepostUri) {
|
||||
// finalize
|
||||
updatePostShadow(postUri, {
|
||||
updatePostShadow(queryClient, postUri, {
|
||||
repostUri: finalRepostUri,
|
||||
})
|
||||
},
|
||||
@@ -184,19 +187,19 @@ export function usePostRepostMutationQueue(
|
||||
|
||||
const queueRepost = useCallback(() => {
|
||||
// optimistically update
|
||||
updatePostShadow(postUri, {
|
||||
updatePostShadow(queryClient, postUri, {
|
||||
repostUri: 'pending',
|
||||
})
|
||||
return queueToggle(true)
|
||||
}, [postUri, queueToggle])
|
||||
}, [queryClient, postUri, queueToggle])
|
||||
|
||||
const queueUnrepost = useCallback(() => {
|
||||
// optimistically update
|
||||
updatePostShadow(postUri, {
|
||||
updatePostShadow(queryClient, postUri, {
|
||||
repostUri: undefined,
|
||||
})
|
||||
return queueToggle(false)
|
||||
}, [postUri, queueToggle])
|
||||
}, [queryClient, postUri, queueToggle])
|
||||
|
||||
return [queueRepost, queueUnrepost]
|
||||
}
|
||||
@@ -234,12 +237,13 @@ function usePostUnrepostMutation(
|
||||
}
|
||||
|
||||
export function usePostDeleteMutation() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<void, Error, {uri: string}>({
|
||||
mutationFn: async ({uri}) => {
|
||||
await getAgent().deletePost(uri)
|
||||
},
|
||||
onSuccess(data, variables) {
|
||||
updatePostShadow(variables.uri, {isDeleted: true})
|
||||
updatePostShadow(queryClient, variables.uri, {isDeleted: true})
|
||||
track('Post:Delete')
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import {useMemo, createContext, useContext} from 'react'
|
||||
import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
import {createContext, useContext, useMemo} from 'react'
|
||||
import {
|
||||
LabelPreference,
|
||||
BskyFeedViewPreference,
|
||||
ModerationOpts,
|
||||
AppBskyActorDefs,
|
||||
BSKY_LABELER_DID,
|
||||
BskyFeedViewPreference,
|
||||
LabelPreference,
|
||||
ModerationOpts,
|
||||
} from '@atproto/api'
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
import {getAge} from '#/lib/strings/time'
|
||||
import {getAgent, useSession} from '#/state/session'
|
||||
import {
|
||||
UsePreferencesQueryResponse,
|
||||
ThreadViewPreferences,
|
||||
} from '#/state/queries/preferences/types'
|
||||
import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {
|
||||
DEFAULT_HOME_FEED_PREFS,
|
||||
DEFAULT_THREAD_VIEW_PREFS,
|
||||
DEFAULT_LOGGED_OUT_PREFERENCES,
|
||||
DEFAULT_THREAD_VIEW_PREFS,
|
||||
} from '#/state/queries/preferences/const'
|
||||
import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
|
||||
import {
|
||||
ThreadViewPreferences,
|
||||
UsePreferencesQueryResponse,
|
||||
} from '#/state/queries/preferences/types'
|
||||
import {getAgent, useSession} from '#/state/session'
|
||||
import {saveLabelers} from '#/state/session/agent-config'
|
||||
|
||||
export * from '#/state/queries/preferences/types'
|
||||
export * from '#/state/queries/preferences/moderation'
|
||||
export * from '#/state/queries/preferences/const'
|
||||
export * from '#/state/queries/preferences/moderation'
|
||||
export * from '#/state/queries/preferences/types'
|
||||
|
||||
export const preferencesQueryKey = ['getPreferences']
|
||||
|
||||
@@ -40,6 +40,8 @@ export function usePreferencesQuery() {
|
||||
queryFn: async () => {
|
||||
const agent = getAgent()
|
||||
|
||||
console.log('GET PREFERENCES', agent.session?.did)
|
||||
|
||||
if (agent.session?.did === undefined) {
|
||||
return DEFAULT_LOGGED_OUT_PREFERENCES
|
||||
} else {
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import {useCallback} from 'react'
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import {
|
||||
AtUri,
|
||||
AppBskyActorDefs,
|
||||
AppBskyActorProfile,
|
||||
AppBskyActorGetProfile,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyActorProfile,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyFeedDefs,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
QueryClient,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
useMutation,
|
||||
QueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||
import {useSession, getAgent} from '../session'
|
||||
import {updateProfileShadow} from '../cache/profile-shadow'
|
||||
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
import {uploadBlob} from '#/lib/api'
|
||||
import {until} from '#/lib/async/until'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||
import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
|
||||
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
||||
import {updateProfileShadow} from '../cache/profile-shadow'
|
||||
import {getAgent, useSession} from '../session'
|
||||
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
|
||||
import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
|
||||
import {ThreadNode} from './post-thread'
|
||||
|
||||
export const RQKEY = (did: string) => ['profile', did]
|
||||
@@ -190,6 +191,7 @@ export function useProfileFollowMutationQueue(
|
||||
logContext: LogEvents['profile:follow']['logContext'] &
|
||||
LogEvents['profile:unfollow']['logContext'],
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const did = profile.did
|
||||
const initialFollowingUri = profile.viewer?.following
|
||||
const followMutation = useProfileFollowMutation(logContext)
|
||||
@@ -215,7 +217,7 @@ export function useProfileFollowMutationQueue(
|
||||
},
|
||||
onSuccess(finalFollowingUri) {
|
||||
// finalize
|
||||
updateProfileShadow(did, {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
followingUri: finalFollowingUri,
|
||||
})
|
||||
},
|
||||
@@ -223,19 +225,19 @@ export function useProfileFollowMutationQueue(
|
||||
|
||||
const queueFollow = useCallback(() => {
|
||||
// optimistically update
|
||||
updateProfileShadow(did, {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
followingUri: 'pending',
|
||||
})
|
||||
return queueToggle(true)
|
||||
}, [did, queueToggle])
|
||||
}, [queryClient, did, queueToggle])
|
||||
|
||||
const queueUnfollow = useCallback(() => {
|
||||
// optimistically update
|
||||
updateProfileShadow(did, {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
followingUri: undefined,
|
||||
})
|
||||
return queueToggle(false)
|
||||
}, [did, queueToggle])
|
||||
}, [queryClient, did, queueToggle])
|
||||
|
||||
return [queueFollow, queueUnfollow]
|
||||
}
|
||||
@@ -269,6 +271,7 @@ function useProfileUnfollowMutation(
|
||||
export function useProfileMuteMutationQueue(
|
||||
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const did = profile.did
|
||||
const initialMuted = profile.viewer?.muted
|
||||
const muteMutation = useProfileMuteMutation()
|
||||
@@ -291,25 +294,25 @@ export function useProfileMuteMutationQueue(
|
||||
},
|
||||
onSuccess(finalMuted) {
|
||||
// finalize
|
||||
updateProfileShadow(did, {muted: finalMuted})
|
||||
updateProfileShadow(queryClient, did, {muted: finalMuted})
|
||||
},
|
||||
})
|
||||
|
||||
const queueMute = useCallback(() => {
|
||||
// optimistically update
|
||||
updateProfileShadow(did, {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
muted: true,
|
||||
})
|
||||
return queueToggle(true)
|
||||
}, [did, queueToggle])
|
||||
}, [queryClient, did, queueToggle])
|
||||
|
||||
const queueUnmute = useCallback(() => {
|
||||
// optimistically update
|
||||
updateProfileShadow(did, {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
muted: false,
|
||||
})
|
||||
return queueToggle(false)
|
||||
}, [did, queueToggle])
|
||||
}, [queryClient, did, queueToggle])
|
||||
|
||||
return [queueMute, queueUnmute]
|
||||
}
|
||||
@@ -341,6 +344,7 @@ function useProfileUnmuteMutation() {
|
||||
export function useProfileBlockMutationQueue(
|
||||
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const did = profile.did
|
||||
const initialBlockingUri = profile.viewer?.blocking
|
||||
const blockMutation = useProfileBlockMutation()
|
||||
@@ -366,7 +370,7 @@ export function useProfileBlockMutationQueue(
|
||||
},
|
||||
onSuccess(finalBlockingUri) {
|
||||
// finalize
|
||||
updateProfileShadow(did, {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
blockingUri: finalBlockingUri,
|
||||
})
|
||||
},
|
||||
@@ -374,19 +378,19 @@ export function useProfileBlockMutationQueue(
|
||||
|
||||
const queueBlock = useCallback(() => {
|
||||
// optimistically update
|
||||
updateProfileShadow(did, {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
blockingUri: 'pending',
|
||||
})
|
||||
return queueToggle(true)
|
||||
}, [did, queueToggle])
|
||||
}, [queryClient, did, queueToggle])
|
||||
|
||||
const queueUnblock = useCallback(() => {
|
||||
// optimistically update
|
||||
updateProfileShadow(did, {
|
||||
updateProfileShadow(queryClient, did, {
|
||||
blockingUri: undefined,
|
||||
})
|
||||
return queueToggle(false)
|
||||
}, [did, queueToggle])
|
||||
}, [queryClient, did, queueToggle])
|
||||
|
||||
return [queueBlock, queueUnblock]
|
||||
}
|
||||
@@ -406,13 +410,14 @@ function useProfileBlockMutation() {
|
||||
},
|
||||
onSuccess(_, {did}) {
|
||||
queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()})
|
||||
resetProfilePostsQueries(did, 1000)
|
||||
resetProfilePostsQueries(queryClient, did, 1000)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function useProfileUnblockMutation() {
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<void, Error, {did: string; blockUri: string}>({
|
||||
mutationFn: async ({blockUri}) => {
|
||||
if (!currentAccount) {
|
||||
@@ -425,7 +430,7 @@ function useProfileUnblockMutation() {
|
||||
})
|
||||
},
|
||||
onSuccess(_, {did}) {
|
||||
resetProfilePostsQueries(did, 1000)
|
||||
resetProfilePostsQueries(queryClient, did, 1000)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
BSKY_LABELER_DID,
|
||||
BskyAgent,
|
||||
} from '@atproto/api'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import {jwtDecode} from 'jwt-decode'
|
||||
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
@@ -178,7 +177,6 @@ function createPersistSessionHandler(
|
||||
}
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const queryClient = useQueryClient()
|
||||
const isDirty = React.useRef(false)
|
||||
const [state, setState] = React.useState<SessionState>({
|
||||
isInitialLoad: true,
|
||||
@@ -211,12 +209,11 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const clearCurrentAccount = React.useCallback(() => {
|
||||
logger.warn(`session: clear current account`)
|
||||
__globalAgent = PUBLIC_BSKY_AGENT
|
||||
queryClient.clear()
|
||||
setStateAndPersist(s => ({
|
||||
...s,
|
||||
currentAccount: undefined,
|
||||
}))
|
||||
}, [setStateAndPersist, queryClient])
|
||||
}, [setStateAndPersist])
|
||||
|
||||
const createAccount = React.useCallback<ApiContext['createAccount']>(
|
||||
async ({
|
||||
@@ -286,14 +283,13 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
)
|
||||
|
||||
__globalAgent = agent
|
||||
queryClient.clear()
|
||||
upsertAccount(account)
|
||||
|
||||
logger.debug(`session: created account`, {}, logger.DebugContext.session)
|
||||
track('Create Account')
|
||||
logEvent('account:create:success', {})
|
||||
},
|
||||
[upsertAccount, queryClient, clearCurrentAccount],
|
||||
[upsertAccount, clearCurrentAccount],
|
||||
)
|
||||
|
||||
const login = React.useCallback<ApiContext['login']>(
|
||||
@@ -334,7 +330,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
__globalAgent = agent
|
||||
// @ts-ignore
|
||||
if (IS_DEV && isWeb) window.agent = agent
|
||||
queryClient.clear()
|
||||
upsertAccount(account)
|
||||
|
||||
logger.debug(`session: logged in`, {}, logger.DebugContext.session)
|
||||
@@ -342,7 +337,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
track('Sign In', {resumedSession: false})
|
||||
logEvent('account:loggedIn', {logContext, withPassword: true})
|
||||
},
|
||||
[upsertAccount, queryClient, clearCurrentAccount],
|
||||
[upsertAccount, clearCurrentAccount],
|
||||
)
|
||||
|
||||
const logout = React.useCallback<ApiContext['logout']>(
|
||||
@@ -373,6 +368,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
persistSession: createPersistSessionHandler(
|
||||
account,
|
||||
({expired, refreshedAccount}) => {
|
||||
console.log('PERSIST CALLBACK', refreshedAccount.did)
|
||||
upsertAccount(refreshedAccount, expired)
|
||||
},
|
||||
{networkErrorCallback: clearCurrentAccount},
|
||||
@@ -411,7 +407,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
|
||||
agent.session = prevSession
|
||||
__globalAgent = agent
|
||||
queryClient.clear()
|
||||
upsertAccount(account)
|
||||
|
||||
if (prevSession.deactivated) {
|
||||
@@ -447,8 +442,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
|
||||
try {
|
||||
const freshAccount = await resumeSessionWithFreshAccount()
|
||||
console.log('RESUMED SESSION', freshAccount.did)
|
||||
__globalAgent = agent
|
||||
queryClient.clear()
|
||||
upsertAccount(freshAccount)
|
||||
} catch (e) {
|
||||
/*
|
||||
@@ -489,7 +484,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
}
|
||||
}
|
||||
},
|
||||
[upsertAccount, queryClient, clearCurrentAccount],
|
||||
[upsertAccount, clearCurrentAccount],
|
||||
)
|
||||
|
||||
const resumeSession = React.useCallback<ApiContext['resumeSession']>(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, {useMemo} from 'react'
|
||||
import {StyleSheet} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
moderateProfile,
|
||||
@@ -9,36 +8,38 @@ import {
|
||||
} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {CenteredView} from '../com/util/Views'
|
||||
import {ListRef} from '../com/util/List'
|
||||
import {ScreenHider} from '#/components/moderation/ScreenHider'
|
||||
import {ProfileLists} from '../com/lists/ProfileLists'
|
||||
import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
|
||||
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
|
||||
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||
import {FAB} from '../com/util/fab/FAB'
|
||||
import {s, colors} from 'lib/styles'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {ComposeIcon2} from 'lib/icons'
|
||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||
import {combinedDisplayName} from 'lib/strings/display-names'
|
||||
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useSession, getAgent} from '#/state/session'
|
||||
import {useModerationOpts} from '#/state/queries/preferences'
|
||||
import {useLabelerInfoQuery} from '#/state/queries/labeler'
|
||||
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
import {useLabelerInfoQuery} from '#/state/queries/labeler'
|
||||
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
||||
import {useModerationOpts} from '#/state/queries/preferences'
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {getAgent, useSession} from '#/state/session'
|
||||
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||
import {ComposeIcon2} from 'lib/icons'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {combinedDisplayName} from 'lib/strings/display-names'
|
||||
import {colors, s} from 'lib/styles'
|
||||
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
|
||||
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
||||
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
|
||||
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
|
||||
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
||||
import {ScreenHider} from '#/components/moderation/ScreenHider'
|
||||
import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
|
||||
import {ProfileLists} from '../com/lists/ProfileLists'
|
||||
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||
import {FAB} from '../com/util/fab/FAB'
|
||||
import {ListRef} from '../com/util/List'
|
||||
import {CenteredView} from '../com/util/Views'
|
||||
|
||||
interface SectionRef {
|
||||
scrollToTop: () => void
|
||||
@@ -48,6 +49,7 @@ type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
|
||||
export function ProfileScreen({route}: Props) {
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
const name =
|
||||
route.params.name === 'me' ? currentAccount?.did : route.params.name
|
||||
const moderationOpts = useModerationOpts()
|
||||
@@ -78,9 +80,9 @@ export function ProfileScreen({route}: Props) {
|
||||
// When we open the profile, we want to reset the posts query if we are blocked.
|
||||
React.useEffect(() => {
|
||||
if (resolvedDid && profile?.viewer?.blockedBy) {
|
||||
resetProfilePostsQueries(resolvedDid)
|
||||
resetProfilePostsQueries(queryClient, resolvedDid)
|
||||
}
|
||||
}, [profile?.viewer?.blockedBy, resolvedDid])
|
||||
}, [queryClient, profile?.viewer?.blockedBy, resolvedDid])
|
||||
|
||||
// Most pushes will happen here, since we will have only placeholder data
|
||||
if (isLoadingDid || isLoadingProfile) {
|
||||
|
||||
Reference in New Issue
Block a user