From 54faa7e176ed2f8644ef4941c8a65522107a84c1 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 16 Nov 2023 12:53:43 -0800 Subject: [PATCH] Remove deprecated models and mobx usage (react-query refactor) (#1934) * Update login page to use service query * Update modal to use session instead of store * Move image sizes cache off store * Update settings to no longer use store * Update link-meta fetch to use agent instead of rootstore * Remove deprecated resolveName() * Delete deprecated link-metas cache * Delete deprecated posts cache * Delete all remaining mobx models, including the root store * Strip out unused mobx observer wrappers --- src/App.native.tsx | 37 +- src/App.web.tsx | 37 +- src/lib/analytics/analytics.tsx | 18 +- src/lib/api/index.ts | 28 - src/lib/async/revertible.ts | 68 - src/lib/link-meta/bsky.ts | 13 +- src/lib/link-meta/link-meta.ts | 12 +- src/lib/media/image-sizes.ts | 34 + src/lib/strings/url-helpers.ts | 2 +- src/state/index.ts | 54 - src/state/models/cache/handle-resolutions.ts | 5 - src/state/models/cache/image-sizes.ts | 38 - src/state/models/cache/link-metas.ts | 44 - src/state/models/cache/posts.ts | 70 - src/state/models/cache/profiles-view.ts | 50 - src/state/models/me.ts | 115 -- src/state/models/root-store.ts | 207 --- src/state/models/session.ts | 43 - src/state/models/ui/shell.ts | 32 - src/state/shell/reminders.e2e.ts | 4 +- src/view/com/auth/LoggedOut.tsx | 5 +- src/view/com/auth/Onboarding.tsx | 5 +- src/view/com/auth/create/Policies.tsx | 4 +- src/view/com/auth/create/Step1.tsx | 2 +- .../com/auth/login/ForgotPasswordForm.tsx | 4 +- src/view/com/auth/login/Login.tsx | 54 +- src/view/com/auth/login/LoginForm.tsx | 4 +- .../com/auth/onboarding/RecommendedFeeds.tsx | 7 +- .../auth/onboarding/RecommendedFeedsItem.tsx | 5 +- .../auth/onboarding/RecommendedFollows.tsx | 7 +- .../com/auth/onboarding/WelcomeDesktop.tsx | 7 +- .../com/auth/onboarding/WelcomeMobile.tsx | 8 +- src/view/com/auth/withAuthRequired.tsx | 5 +- src/view/com/composer/labels/LabelsBtn.tsx | 5 +- .../select-language/SelectLangBtn.tsx | 5 +- .../text-input/mobile/Autocomplete.tsx | 5 +- src/view/com/composer/useExternalLinkFetch.ts | 12 +- .../ImageItem/ImageItem.android.tsx | 1 - .../components/ImageItem/ImageItem.ios.tsx | 1 - src/view/com/modals/BirthDateSettings.tsx | 5 +- src/view/com/modals/ChangeEmail.tsx | 5 +- .../com/modals/ContentFilteringSettings.tsx | 145 +- src/view/com/modals/InviteCodes.tsx | 5 +- src/view/com/modals/LinkWarning.tsx | 11 +- src/view/com/modals/Modal.tsx | 5 +- src/view/com/modals/Modal.web.tsx | 5 +- src/view/com/modals/SelfLabel.tsx | 5 +- src/view/com/modals/ServerInput.tsx | 2 +- src/view/com/modals/VerifyEmail.tsx | 9 +- .../modals/lang-settings/LanguageToggle.tsx | 5 +- .../lang-settings/PostLanguagesSettings.tsx | 5 +- src/view/com/modals/report/Modal.tsx | 6 +- src/view/com/pager/FeedsTabBar.web.tsx | 9 +- src/view/com/pager/FeedsTabBarMobile.tsx | 5 +- src/view/com/posts/FeedSlice.tsx | 5 +- src/view/com/profile/ProfileCard.tsx | 5 +- src/view/com/profile/ProfileSubpageHeader.tsx | 7 +- src/view/com/util/PostMeta.tsx | 5 +- src/view/com/util/SimpleViewHeader.tsx | 5 +- src/view/com/util/ViewHeader.tsx | 9 +- src/view/com/util/fab/FABInner.tsx | 9 +- src/view/com/util/images/AutoSizedImage.tsx | 9 +- .../com/util/load-latest/LoadLatestBtn.tsx | 5 +- src/view/com/util/post-embeds/ListEmbed.tsx | 5 +- src/view/screens/Home.tsx | 213 ++- src/view/screens/LanguageSettings.tsx | 7 +- src/view/screens/Log.tsx | 5 +- src/view/screens/Moderation.tsx | 5 +- src/view/screens/PostThread.tsx | 143 +- src/view/screens/PreferencesHomeFeed.tsx | 7 +- src/view/screens/PreferencesThreads.tsx | 7 +- src/view/screens/ProfileFeed.tsx | 11 +- src/view/screens/SavedFeeds.tsx | 5 +- src/view/screens/Settings.tsx | 1211 ++++++++--------- src/view/shell/Drawer.tsx | 13 +- src/view/shell/bottom-bar/BottomBar.tsx | 7 +- src/view/shell/bottom-bar/BottomBarWeb.tsx | 5 +- src/view/shell/desktop/Feeds.tsx | 5 +- src/view/shell/desktop/LeftNav.tsx | 19 +- src/view/shell/desktop/RightNav.tsx | 9 +- src/view/shell/index.web.tsx | 5 +- 81 files changed, 1084 insertions(+), 1941 deletions(-) delete mode 100644 src/lib/async/revertible.ts create mode 100644 src/lib/media/image-sizes.ts delete mode 100644 src/state/index.ts delete mode 100644 src/state/models/cache/handle-resolutions.ts delete mode 100644 src/state/models/cache/image-sizes.ts delete mode 100644 src/state/models/cache/link-metas.ts delete mode 100644 src/state/models/cache/posts.ts delete mode 100644 src/state/models/cache/profiles-view.ts delete mode 100644 src/state/models/me.ts delete mode 100644 src/state/models/root-store.ts delete mode 100644 src/state/models/session.ts delete mode 100644 src/state/models/ui/shell.ts diff --git a/src/App.native.tsx b/src/App.native.tsx index 3f49eb11..af4c3be8 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -5,7 +5,6 @@ import React, {useState, useEffect} from 'react' import {RootSiblingParent} from 'react-native-root-siblings' import * as SplashScreen from 'expo-splash-screen' import {GestureHandlerRootView} from 'react-native-gesture-handler' -import {observer} from 'mobx-react-lite' import {QueryClientProvider} from '@tanstack/react-query' import 'view/icons' @@ -16,7 +15,6 @@ import {listenSessionDropped} from './state/events' import {useColorMode} from 'state/shell' import {ThemeProvider} from 'lib/ThemeContext' import {s} from 'lib/styles' -import {RootStoreModel, setupState, RootStoreProvider} from './state' import {Shell} from 'view/shell' import * as notifications from 'lib/notifications/notifications' import * as analytics from 'lib/analytics/analytics' @@ -44,21 +42,12 @@ i18n.activate('en') SplashScreen.preventAutoHideAsync() -const InnerApp = observer(function AppImpl() { +function InnerApp() { const colorMode = useColorMode() const {isInitialLoad} = useSession() const {resumeSession} = useSessionApi() - const [rootStore, setRootStore] = useState( - undefined, - ) // init - useEffect(() => { - setupState().then(store => { - setRootStore(store) - }) - }, []) - useEffect(() => { initReminders() analytics.init() @@ -72,7 +61,7 @@ const InnerApp = observer(function AppImpl() { }, [resumeSession]) // show nothing prior to init - if (!rootStore || isInitialLoad) { + if (isInitialLoad) { // TODO add a loading state return null } @@ -85,22 +74,20 @@ const InnerApp = observer(function AppImpl() { - - - {/* All components should be within this provider */} - - - - - - - - + + {/* All components should be within this provider */} + + + + + + + ) -}) +} function App() { const [isReady, setReady] = useState(false) diff --git a/src/App.web.tsx b/src/App.web.tsx index e1f4e803..67fa3dcc 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -1,7 +1,6 @@ import 'lib/sentry' // must be near top import React, {useState, useEffect} from 'react' -import {observer} from 'mobx-react-lite' import {QueryClientProvider} from '@tanstack/react-query' import {SafeAreaProvider} from 'react-native-safe-area-context' import {RootSiblingParent} from 'react-native-root-siblings' @@ -12,7 +11,6 @@ import {init as initPersistedState} from '#/state/persisted' import {init as initReminders} from '#/state/shell/reminders' import {useColorMode} from 'state/shell' import * as analytics from 'lib/analytics/analytics' -import {RootStoreModel, setupState, RootStoreProvider} from './state' import {Shell} from 'view/shell/index' import {ToastContainer} from 'view/com/util/Toast.web' import {ThemeProvider} from 'lib/ThemeContext' @@ -34,21 +32,12 @@ import { import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' import * as persisted from '#/state/persisted' -const InnerApp = observer(function AppImpl() { +function InnerApp() { const {isInitialLoad} = useSession() const {resumeSession} = useSessionApi() const colorMode = useColorMode() - const [rootStore, setRootStore] = useState( - undefined, - ) // init - useEffect(() => { - setupState().then(store => { - setRootStore(store) - }) - }, []) - useEffect(() => { initReminders() analytics.init() @@ -59,7 +48,7 @@ const InnerApp = observer(function AppImpl() { }, [resumeSession]) // show nothing prior to init - if (!rootStore || isInitialLoad) { + if (isInitialLoad) { // TODO add a loading state return null } @@ -72,22 +61,20 @@ const InnerApp = observer(function AppImpl() { - - - {/* All components should be within this provider */} - - - - - - - - + + {/* All components should be within this provider */} + + + + + + + ) -}) +} function App() { const [isReady, setReady] = useState(false) diff --git a/src/lib/analytics/analytics.tsx b/src/lib/analytics/analytics.tsx index 4b955b36..3a8254eb 100644 --- a/src/lib/analytics/analytics.tsx +++ b/src/lib/analytics/analytics.tsx @@ -7,13 +7,21 @@ import { useAnalytics as useAnalyticsOrig, ClientMethods, } from '@segment/analytics-react-native' -import {AppInfo} from 'state/models/root-store' +import {z} from 'zod' import {useSession} from '#/state/session' import {sha256} from 'js-sha256' import {ScreenEvent, TrackEvent} from './types' import {logger} from '#/logger' import {listenSessionLoaded} from '#/state/events' +export const appInfo = z.object({ + build: z.string().optional(), + name: z.string().optional(), + namespace: z.string().optional(), + version: z.string().optional(), +}) +export type AppInfo = z.infer + const segmentClient = createClient({ writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI', trackAppLifecycleEvents: false, @@ -128,7 +136,11 @@ async function writeAppInfo(value: AppInfo) { await AsyncStorage.setItem('BSKY_APP_INFO', JSON.stringify(value)) } -async function readAppInfo(): Promise | undefined> { +async function readAppInfo(): Promise { const rawData = await AsyncStorage.getItem('BSKY_APP_INFO') - return rawData ? JSON.parse(rawData) : undefined + const obj = rawData ? JSON.parse(rawData) : undefined + if (!obj || typeof obj !== 'object') { + return undefined + } + return obj } diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index 92620c45..a78abcac 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -10,7 +10,6 @@ import { RichText, } from '@atproto/api' import {AtUri} from '@atproto/api' -import {RootStoreModel} from 'state/models/root-store' import {isNetworkError} from 'lib/strings/errors' import {LinkMeta} from '../link-meta/link-meta' import {isWeb} from 'platform/detection' @@ -26,33 +25,6 @@ export interface ExternalEmbedDraft { localThumb?: ImageModel } -export async function resolveName(store: RootStoreModel, didOrHandle: string) { - if (!didOrHandle) { - throw new Error('Invalid handle: ""') - } - if (didOrHandle.startsWith('did:')) { - return didOrHandle - } - - // we run the resolution always to ensure freshness - const promise = store.agent - .resolveHandle({ - handle: didOrHandle, - }) - .then(res => { - store.handleResolutions.cache.set(didOrHandle, res.data.did) - return res.data.did - }) - - // but we can return immediately if it's cached - const cached = store.handleResolutions.cache.get(didOrHandle) - if (cached) { - return cached - } - - return promise -} - export async function uploadBlob( agent: BskyAgent, blob: string, diff --git a/src/lib/async/revertible.ts b/src/lib/async/revertible.ts deleted file mode 100644 index 43383b61..00000000 --- a/src/lib/async/revertible.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {runInAction} from 'mobx' -import {deepObserve} from 'mobx-utils' -import set from 'lodash.set' - -const ongoingActions = new Set() - -/** - * This is a TypeScript function that optimistically updates data on the client-side before sending a - * request to the server and rolling back changes if the request fails. - * @param {T} model - The object or record that needs to be updated optimistically. - * @param preUpdate - `preUpdate` is a function that is called before the server update is executed. It - * can be used to perform any necessary actions or updates on the model or UI before the server update - * is initiated. - * @param serverUpdate - `serverUpdate` is a function that returns a Promise representing the server - * update operation. This function is called after the previous state of the model has been recorded - * and the `preUpdate` function has been executed. If the server update is successful, the `postUpdate` - * function is called with the result - * @param [postUpdate] - `postUpdate` is an optional callback function that will be called after the - * server update is successful. It takes in the response from the server update as its parameter. If - * this parameter is not provided, nothing will happen after the server update. - * @returns A Promise that resolves to `void`. - */ -export const updateDataOptimistically = async < - T extends Record, - U, ->( - model: T, - preUpdate: () => void, - serverUpdate: () => Promise, - postUpdate?: (res: U) => void, -): Promise => { - if (ongoingActions.has(model)) { - return - } - ongoingActions.add(model) - - const prevState: Map = new Map() - const dispose = deepObserve(model, (change, path) => { - if (change.observableKind === 'object') { - if (change.type === 'update') { - prevState.set( - [path, change.name].filter(Boolean).join('.'), - change.oldValue, - ) - } else if (change.type === 'add') { - prevState.set([path, change.name].filter(Boolean).join('.'), undefined) - } - } - }) - preUpdate() - dispose() - - try { - const res = await serverUpdate() - runInAction(() => { - postUpdate?.(res) - }) - } catch (error) { - runInAction(() => { - prevState.forEach((value, path) => { - set(model, path, value) - }) - }) - throw error - } finally { - ongoingActions.delete(model) - } -} diff --git a/src/lib/link-meta/bsky.ts b/src/lib/link-meta/bsky.ts index 78d75533..322b0233 100644 --- a/src/lib/link-meta/bsky.ts +++ b/src/lib/link-meta/bsky.ts @@ -1,9 +1,8 @@ -import {AppBskyFeedPost} from '@atproto/api' +import {AppBskyFeedPost, BskyAgent} from '@atproto/api' import * as apilib from 'lib/api/index' import {LikelyType, LinkMeta} from './link-meta' // import {match as matchRoute} from 'view/routes' import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers' -import {RootStoreModel} from 'state/index' import {ComposerOptsQuote} from 'state/shell/composer' import {useGetPost} from '#/state/queries/post' @@ -23,7 +22,7 @@ import {useGetPost} from '#/state/queries/post' // remove once that's implemented // -prf export async function extractBskyMeta( - store: RootStoreModel, + agent: BskyAgent, url: string, ): Promise { url = convertBskyAppUrlIfNeeded(url) @@ -120,13 +119,13 @@ export async function getPostAsQuote( } export async function getFeedAsEmbed( - store: RootStoreModel, + agent: BskyAgent, url: string, ): Promise { url = convertBskyAppUrlIfNeeded(url) const [_0, user, _1, rkey] = url.split('/').filter(Boolean) const feed = makeRecordUri(user, 'app.bsky.feed.generator', rkey) - const res = await store.agent.app.bsky.feed.getFeedGenerator({feed}) + const res = await agent.app.bsky.feed.getFeedGenerator({feed}) return { isLoading: false, uri: feed, @@ -146,13 +145,13 @@ export async function getFeedAsEmbed( } export async function getListAsEmbed( - store: RootStoreModel, + agent: BskyAgent, url: string, ): Promise { url = convertBskyAppUrlIfNeeded(url) const [_0, user, _1, rkey] = url.split('/').filter(Boolean) const list = makeRecordUri(user, 'app.bsky.graph.list', rkey) - const res = await store.agent.app.bsky.graph.getList({list}) + const res = await agent.app.bsky.graph.getList({list}) return { isLoading: false, uri: list, diff --git a/src/lib/link-meta/link-meta.ts b/src/lib/link-meta/link-meta.ts index c490fa29..c17dee51 100644 --- a/src/lib/link-meta/link-meta.ts +++ b/src/lib/link-meta/link-meta.ts @@ -1,5 +1,5 @@ +import {BskyAgent} from '@atproto/api' import {isBskyAppUrl} from '../strings/url-helpers' -import {RootStoreModel} from 'state/index' import {extractBskyMeta} from './bsky' import {LINK_META_PROXY} from 'lib/constants' @@ -23,12 +23,12 @@ export interface LinkMeta { } export async function getLinkMeta( - store: RootStoreModel, + agent: BskyAgent, url: string, timeout = 5e3, ): Promise { if (isBskyAppUrl(url)) { - return extractBskyMeta(store, url) + return extractBskyMeta(agent, url) } let urlp @@ -55,9 +55,9 @@ export async function getLinkMeta( const to = setTimeout(() => controller.abort(), timeout || 5e3) const response = await fetch( - `${LINK_META_PROXY( - store.session.currentSession?.service || '', - )}${encodeURIComponent(url)}`, + `${LINK_META_PROXY(agent.service.toString() || '')}${encodeURIComponent( + url, + )}`, {signal: controller.signal}, ) diff --git a/src/lib/media/image-sizes.ts b/src/lib/media/image-sizes.ts new file mode 100644 index 00000000..4ea95ea2 --- /dev/null +++ b/src/lib/media/image-sizes.ts @@ -0,0 +1,34 @@ +import {Image} from 'react-native' +import type {Dimensions} from 'lib/media/types' + +const sizes: Map = new Map() +const activeRequests: Map> = new Map() + +export function get(uri: string): Dimensions | undefined { + return sizes.get(uri) +} + +export async function fetch(uri: string): Promise { + const Dimensions = sizes.get(uri) + if (Dimensions) { + return Dimensions + } + + const prom = + activeRequests.get(uri) || + new Promise(resolve => { + Image.getSize( + uri, + (width: number, height: number) => resolve({width, height}), + (err: any) => { + console.error('Failed to fetch image dimensions for', uri, err) + resolve({width: 0, height: 0}) + }, + ) + }) + activeRequests.set(uri, prom) + const res = await prom + activeRequests.delete(uri) + sizes.set(uri, res) + return res +} diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts index 106d2ca3..d06bd802 100644 --- a/src/lib/strings/url-helpers.ts +++ b/src/lib/strings/url-helpers.ts @@ -1,5 +1,5 @@ import {AtUri} from '@atproto/api' -import {PROD_SERVICE} from 'state/index' +import {PROD_SERVICE} from 'lib/constants' import TLDs from 'tlds' import psl from 'psl' diff --git a/src/state/index.ts b/src/state/index.ts deleted file mode 100644 index 55dcae6d..00000000 --- a/src/state/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {autorun} from 'mobx' -import {AppState, Platform} from 'react-native' -import {BskyAgent} from '@atproto/api' -import {RootStoreModel} from './models/root-store' -import * as apiPolyfill from 'lib/api/api-polyfill' -import * as storage from 'lib/storage' -import {logger} from '#/logger' - -export const LOCAL_DEV_SERVICE = - Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' -export const STAGING_SERVICE = 'https://staging.bsky.dev' -export const PROD_SERVICE = 'https://bsky.social' -export const DEFAULT_SERVICE = PROD_SERVICE -const ROOT_STATE_STORAGE_KEY = 'root' -const STATE_FETCH_INTERVAL = 15e3 - -export async function setupState(serviceUri = DEFAULT_SERVICE) { - let rootStore: RootStoreModel - let data: any - - apiPolyfill.doPolyfill() - - rootStore = new RootStoreModel(new BskyAgent({service: serviceUri})) - try { - data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {} - logger.debug('Initial hydrate', {hasSession: !!data.session}) - rootStore.hydrate(data) - } catch (e: any) { - logger.error('Failed to load state from storage', {error: e}) - } - rootStore.attemptSessionResumption() - - // track changes & save to storage - autorun(() => { - const snapshot = rootStore.serialize() - storage.save(ROOT_STATE_STORAGE_KEY, snapshot) - }) - - // periodic state fetch - setInterval(() => { - // NOTE - // this must ONLY occur when the app is active, as the bg-fetch handler - // will wake up the thread and cause this interval to fire, which in - // turn schedules a bunch of work at a poor time - // -prf - if (AppState.currentState === 'active') { - rootStore.updateSessionState() - } - }, STATE_FETCH_INTERVAL) - - return rootStore -} - -export {useStores, RootStoreModel, RootStoreProvider} from './models/root-store' diff --git a/src/state/models/cache/handle-resolutions.ts b/src/state/models/cache/handle-resolutions.ts deleted file mode 100644 index 2e2b6966..00000000 --- a/src/state/models/cache/handle-resolutions.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {LRUMap} from 'lru_map' - -export class HandleResolutionsCache { - cache: LRUMap = new LRUMap(500) -} diff --git a/src/state/models/cache/image-sizes.ts b/src/state/models/cache/image-sizes.ts deleted file mode 100644 index c30a68f4..00000000 --- a/src/state/models/cache/image-sizes.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {Image} from 'react-native' -import type {Dimensions} from 'lib/media/types' - -export class ImageSizesCache { - sizes: Map = new Map() - activeRequests: Map> = new Map() - - constructor() {} - - get(uri: string): Dimensions | undefined { - return this.sizes.get(uri) - } - - async fetch(uri: string): Promise { - const Dimensions = this.sizes.get(uri) - if (Dimensions) { - return Dimensions - } - - const prom = - this.activeRequests.get(uri) || - new Promise(resolve => { - Image.getSize( - uri, - (width: number, height: number) => resolve({width, height}), - (err: any) => { - console.error('Failed to fetch image dimensions for', uri, err) - resolve({width: 0, height: 0}) - }, - ) - }) - this.activeRequests.set(uri, prom) - const res = await prom - this.activeRequests.delete(uri) - this.sizes.set(uri, res) - return res - } -} diff --git a/src/state/models/cache/link-metas.ts b/src/state/models/cache/link-metas.ts deleted file mode 100644 index 607968c8..00000000 --- a/src/state/models/cache/link-metas.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {makeAutoObservable} from 'mobx' -import {LRUMap} from 'lru_map' -import {RootStoreModel} from '../root-store' -import {LinkMeta, getLinkMeta} from 'lib/link-meta/link-meta' - -type CacheValue = Promise | LinkMeta -export class LinkMetasCache { - cache: LRUMap = new LRUMap(100) - - constructor(public rootStore: RootStoreModel) { - makeAutoObservable( - this, - { - rootStore: false, - cache: false, - }, - {autoBind: true}, - ) - } - - // public api - // = - - async getLinkMeta(url: string) { - const cached = this.cache.get(url) - if (cached) { - try { - return await cached - } catch (e) { - // ignore, we'll try again - } - } - try { - const promise = getLinkMeta(this.rootStore, url) - this.cache.set(url, promise) - const res = await promise - this.cache.set(url, res) - return res - } catch (e) { - this.cache.delete(url) - throw e - } - } -} diff --git a/src/state/models/cache/posts.ts b/src/state/models/cache/posts.ts deleted file mode 100644 index d3632f43..00000000 --- a/src/state/models/cache/posts.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {LRUMap} from 'lru_map' -import {RootStoreModel} from '../root-store' -import { - AppBskyFeedDefs, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - AppBskyFeedPost, -} from '@atproto/api' - -type PostView = AppBskyFeedDefs.PostView - -export class PostsCache { - cache: LRUMap = new LRUMap(500) - - constructor(public rootStore: RootStoreModel) {} - - set(uri: string, postView: PostView) { - this.cache.set(uri, postView) - if (postView.author.handle) { - this.rootStore.handleResolutions.cache.set( - postView.author.handle, - postView.author.did, - ) - } - } - - fromFeedItem(feedItem: AppBskyFeedDefs.FeedViewPost) { - this.set(feedItem.post.uri, feedItem.post) - if ( - feedItem.reply?.parent && - AppBskyFeedDefs.isPostView(feedItem.reply?.parent) - ) { - this.set(feedItem.reply.parent.uri, feedItem.reply.parent) - } - const embed = feedItem.post.embed - if ( - AppBskyEmbedRecord.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record) && - AppBskyFeedPost.isRecord(embed.record.value) && - AppBskyFeedPost.validateRecord(embed.record.value).success - ) { - this.set(embed.record.uri, embedViewToPostView(embed.record)) - } - if ( - AppBskyEmbedRecordWithMedia.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record?.record) && - AppBskyFeedPost.isRecord(embed.record.record.value) && - AppBskyFeedPost.validateRecord(embed.record.record.value).success - ) { - this.set( - embed.record.record.uri, - embedViewToPostView(embed.record.record), - ) - } - } -} - -function embedViewToPostView( - embedView: AppBskyEmbedRecord.ViewRecord, -): PostView { - return { - $type: 'app.bsky.feed.post#view', - uri: embedView.uri, - cid: embedView.cid, - author: embedView.author, - record: embedView.value, - indexedAt: embedView.indexedAt, - labels: embedView.labels, - } -} diff --git a/src/state/models/cache/profiles-view.ts b/src/state/models/cache/profiles-view.ts deleted file mode 100644 index e5a9be58..00000000 --- a/src/state/models/cache/profiles-view.ts +++ /dev/null @@ -1,50 +0,0 @@ -import {makeAutoObservable} from 'mobx' -import {LRUMap} from 'lru_map' -import {RootStoreModel} from '../root-store' -import {AppBskyActorGetProfile as GetProfile} from '@atproto/api' - -type CacheValue = Promise | GetProfile.Response -export class ProfilesCache { - cache: LRUMap = new LRUMap(100) - - constructor(public rootStore: RootStoreModel) { - makeAutoObservable( - this, - { - rootStore: false, - cache: false, - }, - {autoBind: true}, - ) - } - - // public api - // = - - async getProfile(did: string) { - const cached = this.cache.get(did) - if (cached) { - try { - return await cached - } catch (e) { - // ignore, we'll try again - } - } - try { - const promise = this.rootStore.agent.getProfile({ - actor: did, - }) - this.cache.set(did, promise) - const res = await promise - this.cache.set(did, res) - return res - } catch (e) { - this.cache.delete(did) - throw e - } - } - - overwrite(did: string, res: GetProfile.Response) { - this.cache.set(did, res) - } -} diff --git a/src/state/models/me.ts b/src/state/models/me.ts deleted file mode 100644 index 1e802fb7..00000000 --- a/src/state/models/me.ts +++ /dev/null @@ -1,115 +0,0 @@ -import {makeAutoObservable, runInAction} from 'mobx' -import {RootStoreModel} from './root-store' -import {isObj, hasProp} from 'lib/type-guards' -import {logger} from '#/logger' - -const PROFILE_UPDATE_INTERVAL = 10 * 60 * 1e3 // 10min - -export class MeModel { - did: string = '' - handle: string = '' - displayName: string = '' - description: string = '' - avatar: string = '' - followsCount: number | undefined - followersCount: number | undefined - lastProfileStateUpdate = Date.now() - - constructor(public rootStore: RootStoreModel) { - makeAutoObservable( - this, - {rootStore: false, serialize: false, hydrate: false}, - {autoBind: true}, - ) - } - - clear() { - this.rootStore.profiles.cache.clear() - this.rootStore.posts.cache.clear() - this.did = '' - this.handle = '' - this.displayName = '' - this.description = '' - this.avatar = '' - } - - serialize(): unknown { - return { - did: this.did, - handle: this.handle, - displayName: this.displayName, - description: this.description, - avatar: this.avatar, - } - } - - hydrate(v: unknown) { - if (isObj(v)) { - let did, handle, displayName, description, avatar - if (hasProp(v, 'did') && typeof v.did === 'string') { - did = v.did - } - if (hasProp(v, 'handle') && typeof v.handle === 'string') { - handle = v.handle - } - if (hasProp(v, 'displayName') && typeof v.displayName === 'string') { - displayName = v.displayName - } - if (hasProp(v, 'description') && typeof v.description === 'string') { - description = v.description - } - if (hasProp(v, 'avatar') && typeof v.avatar === 'string') { - avatar = v.avatar - } - if (did && handle) { - this.did = did - this.handle = handle - this.displayName = displayName || '' - this.description = description || '' - this.avatar = avatar || '' - } - } - } - - async load() { - const sess = this.rootStore.session - logger.debug('MeModel:load', {hasSession: sess.hasSession}) - if (sess.hasSession) { - this.did = sess.currentSession?.did || '' - await this.fetchProfile() - this.rootStore.emitSessionLoaded() - } else { - this.clear() - } - } - - async updateIfNeeded() { - if (Date.now() - this.lastProfileStateUpdate > PROFILE_UPDATE_INTERVAL) { - logger.debug('Updating me profile information') - this.lastProfileStateUpdate = Date.now() - await this.fetchProfile() - } - } - - async fetchProfile() { - const profile = await this.rootStore.agent.getProfile({ - actor: this.did, - }) - runInAction(() => { - if (profile?.data) { - this.displayName = profile.data.displayName || '' - this.description = profile.data.description || '' - this.avatar = profile.data.avatar || '' - this.handle = profile.data.handle || '' - this.followsCount = profile.data.followsCount - this.followersCount = profile.data.followersCount - } else { - this.displayName = '' - this.description = '' - this.avatar = '' - this.followsCount = profile.data.followsCount - this.followersCount = undefined - } - }) - } -} diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts deleted file mode 100644 index 6d7c2c12..00000000 --- a/src/state/models/root-store.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * The root store is the base of all modeled state. - */ - -import {makeAutoObservable} from 'mobx' -import {BskyAgent} from '@atproto/api' -import {createContext, useContext} from 'react' -import {DeviceEventEmitter, EmitterSubscription} from 'react-native' -import {z} from 'zod' -import {isObj, hasProp} from 'lib/type-guards' -import {SessionModel} from './session' -import {ShellUiModel} from './ui/shell' -import {HandleResolutionsCache} from './cache/handle-resolutions' -import {ProfilesCache} from './cache/profiles-view' -import {PostsCache} from './cache/posts' -import {LinkMetasCache} from './cache/link-metas' -import {MeModel} from './me' -import {resetToTab} from '../../Navigation' -import {ImageSizesCache} from './cache/image-sizes' -import {reset as resetNavigation} from '../../Navigation' -import {logger} from '#/logger' - -// TEMPORARY (APP-700) -// remove after backend testing finishes -// -prf -import {applyDebugHeader} from 'lib/api/debug-appview-proxy-header' - -export const appInfo = z.object({ - build: z.string(), - name: z.string(), - namespace: z.string(), - version: z.string(), -}) -export type AppInfo = z.infer - -export class RootStoreModel { - agent: BskyAgent - appInfo?: AppInfo - session = new SessionModel(this) - shell = new ShellUiModel(this) - me = new MeModel(this) - handleResolutions = new HandleResolutionsCache() - profiles = new ProfilesCache(this) - posts = new PostsCache(this) - linkMetas = new LinkMetasCache(this) - imageSizes = new ImageSizesCache() - - constructor(agent: BskyAgent) { - this.agent = agent - makeAutoObservable(this, { - agent: false, - serialize: false, - hydrate: false, - }) - } - - setAppInfo(info: AppInfo) { - this.appInfo = info - } - - serialize(): unknown { - return { - appInfo: this.appInfo, - me: this.me.serialize(), - } - } - - hydrate(v: unknown) { - if (isObj(v)) { - if (hasProp(v, 'appInfo')) { - const appInfoParsed = appInfo.safeParse(v.appInfo) - if (appInfoParsed.success) { - this.setAppInfo(appInfoParsed.data) - } - } - if (hasProp(v, 'me')) { - this.me.hydrate(v.me) - } - } - } - - /** - * Called during init to resume any stored session. - */ - async attemptSessionResumption() {} - - /** - * Called by the session model. Refreshes session-oriented state. - */ - async handleSessionChange( - agent: BskyAgent, - {hadSession}: {hadSession: boolean}, - ) { - logger.debug('RootStoreModel:handleSessionChange') - this.agent = agent - applyDebugHeader(this.agent) - this.me.clear() - await this.me.load() - if (!hadSession) { - await resetNavigation() - } - this.emitSessionReady() - } - - /** - * Called by the session model. Handles session drops by informing the user. - */ - async handleSessionDrop() { - logger.debug('RootStoreModel:handleSessionDrop') - resetToTab('HomeTab') - this.me.clear() - this.emitSessionDropped() - } - - /** - * Clears all session-oriented state, previously called on LOGOUT - */ - clearAllSessionState() { - logger.debug('RootStoreModel:clearAllSessionState') - resetToTab('HomeTab') - this.me.clear() - } - - /** - * Periodic poll for new session state. - */ - async updateSessionState() { - if (!this.session.hasSession) { - return - } - try { - await this.me.updateIfNeeded() - } catch (e: any) { - logger.error('Failed to fetch latest state', {error: e}) - } - } - - // global event bus - // = - // - some events need to be passed around between views and models - // in order to keep state in sync; these methods are for that - - // a post was deleted by the local user - onPostDeleted(handler: (uri: string) => void): EmitterSubscription { - return DeviceEventEmitter.addListener('post-deleted', handler) - } - emitPostDeleted(uri: string) { - DeviceEventEmitter.emit('post-deleted', uri) - } - - // a list was deleted by the local user - onListDeleted(handler: (uri: string) => void): EmitterSubscription { - return DeviceEventEmitter.addListener('list-deleted', handler) - } - emitListDeleted(uri: string) { - DeviceEventEmitter.emit('list-deleted', uri) - } - - // the session has started and been fully hydrated - onSessionLoaded(handler: () => void): EmitterSubscription { - return DeviceEventEmitter.addListener('session-loaded', handler) - } - emitSessionLoaded() { - DeviceEventEmitter.emit('session-loaded') - } - - // the session has completed all setup; good for post-initialization behaviors like triggering modals - onSessionReady(handler: () => void): EmitterSubscription { - return DeviceEventEmitter.addListener('session-ready', handler) - } - emitSessionReady() { - DeviceEventEmitter.emit('session-ready') - } - - // the session was dropped due to bad/expired refresh tokens - onSessionDropped(handler: () => void): EmitterSubscription { - return DeviceEventEmitter.addListener('session-dropped', handler) - } - emitSessionDropped() { - DeviceEventEmitter.emit('session-dropped') - } - - // the current screen has changed - // TODO is this still needed? - onNavigation(handler: () => void): EmitterSubscription { - return DeviceEventEmitter.addListener('navigation', handler) - } - emitNavigation() { - DeviceEventEmitter.emit('navigation') - } - - // a "soft reset" typically means scrolling to top and loading latest - // but it can depend on the screen - onScreenSoftReset(handler: () => void): EmitterSubscription { - return DeviceEventEmitter.addListener('screen-soft-reset', handler) - } - emitScreenSoftReset() { - DeviceEventEmitter.emit('screen-soft-reset') - } -} - -const throwawayInst = new RootStoreModel( - new BskyAgent({service: 'http://localhost'}), -) // this will be replaced by the loader, we just need to supply a value at init -const RootStoreContext = createContext(throwawayInst) -export const RootStoreProvider = RootStoreContext.Provider -export const useStores = () => useContext(RootStoreContext) diff --git a/src/state/models/session.ts b/src/state/models/session.ts deleted file mode 100644 index cc681ad3..00000000 --- a/src/state/models/session.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {makeAutoObservable} from 'mobx' -import { - BskyAgent, - ComAtprotoServerDescribeServer as DescribeServer, -} from '@atproto/api' -import {RootStoreModel} from './root-store' - -export type ServiceDescription = DescribeServer.OutputSchema - -export class SessionModel { - data: any = {} - - constructor(public rootStore: RootStoreModel) { - makeAutoObservable(this, { - rootStore: false, - hasSession: false, - }) - } - - get currentSession(): any { - return undefined - } - - get hasSession() { - return false - } - - clear() {} - - /** - * Helper to fetch the accounts config settings from an account. - */ - async describeService(service: string): Promise { - const agent = new BskyAgent({service}) - const res = await agent.com.atproto.server.describeServer({}) - return res.data - } - - /** - * Reloads the session from the server. Useful when account details change, like the handle. - */ - async reloadFromServer() {} -} diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts deleted file mode 100644 index 18287c95..00000000 --- a/src/state/models/ui/shell.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {RootStoreModel} from '../root-store' -import {makeAutoObservable} from 'mobx' -import { - shouldRequestEmailConfirmation, - setEmailConfirmationRequested, -} from '#/state/shell/reminders' -import {unstable__openModal} from '#/state/modals' - -export type ColorMode = 'system' | 'light' | 'dark' - -export function isColorMode(v: unknown): v is ColorMode { - return v === 'system' || v === 'light' || v === 'dark' -} - -export class ShellUiModel { - constructor(public rootStore: RootStoreModel) { - makeAutoObservable(this, { - rootStore: false, - }) - - this.setupLoginModals() - } - - setupLoginModals() { - this.rootStore.onSessionReady(() => { - if (shouldRequestEmailConfirmation(this.rootStore.session)) { - unstable__openModal({name: 'verify-email', showReminder: true}) - setEmailConfirmationRequested() - } - }) - } -} diff --git a/src/state/shell/reminders.e2e.ts b/src/state/shell/reminders.e2e.ts index d7703e9e..4bd11bf0 100644 --- a/src/state/shell/reminders.e2e.ts +++ b/src/state/shell/reminders.e2e.ts @@ -1,6 +1,4 @@ -import {SessionModel} from '../models/session' - -export function shouldRequestEmailConfirmation(_session: SessionModel) { +export function shouldRequestEmailConfirmation() { return false } diff --git a/src/view/com/auth/LoggedOut.tsx b/src/view/com/auth/LoggedOut.tsx index 0d817296..3505e86a 100644 --- a/src/view/com/auth/LoggedOut.tsx +++ b/src/view/com/auth/LoggedOut.tsx @@ -1,6 +1,5 @@ import React from 'react' import {SafeAreaView} from 'react-native' -import {observer} from 'mobx-react-lite' import {Login} from 'view/com/auth/login/Login' import {CreateAccount} from 'view/com/auth/create/CreateAccount' import {ErrorBoundary} from 'view/com/util/ErrorBoundary' @@ -16,7 +15,7 @@ enum ScreenState { S_CreateAccount, } -export const LoggedOut = observer(function LoggedOutImpl() { +export function LoggedOut() { const pal = usePalette('default') const setMinimalShellMode = useSetMinimalShellMode() const {screen} = useAnalytics() @@ -58,4 +57,4 @@ export const LoggedOut = observer(function LoggedOutImpl() { ) -}) +} diff --git a/src/view/com/auth/Onboarding.tsx b/src/view/com/auth/Onboarding.tsx index 994f4c14..3a17eb4f 100644 --- a/src/view/com/auth/Onboarding.tsx +++ b/src/view/com/auth/Onboarding.tsx @@ -1,6 +1,5 @@ import React from 'react' import {SafeAreaView} from 'react-native' -import {observer} from 'mobx-react-lite' import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {s} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' @@ -10,7 +9,7 @@ import {RecommendedFollows} from './onboarding/RecommendedFollows' import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' import {useOnboardingState, useOnboardingDispatch} from '#/state/shell' -export const Onboarding = observer(function OnboardingImpl() { +export function Onboarding() { const pal = usePalette('default') const setMinimalShellMode = useSetMinimalShellMode() const onboardingState = useOnboardingState() @@ -38,4 +37,4 @@ export const Onboarding = observer(function OnboardingImpl() { ) -}) +} diff --git a/src/view/com/auth/create/Policies.tsx b/src/view/com/auth/create/Policies.tsx index 7d10f32f..a52f0753 100644 --- a/src/view/com/auth/create/Policies.tsx +++ b/src/view/com/auth/create/Policies.tsx @@ -4,12 +4,14 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' +import {ComAtprotoServerDescribeServer} from '@atproto/api' import {TextLink} from '../../util/Link' import {Text} from '../../util/text/Text' import {s, colors} from 'lib/styles' -import {ServiceDescription} from 'state/models/session' import {usePalette} from 'lib/hooks/usePalette' +type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema + export const Policies = ({ serviceDescription, needsGuardian, diff --git a/src/view/com/auth/create/Step1.tsx b/src/view/com/auth/create/Step1.tsx index ab47b411..c9d19e86 100644 --- a/src/view/com/auth/create/Step1.tsx +++ b/src/view/com/auth/create/Step1.tsx @@ -13,7 +13,7 @@ import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'state/index' +import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'lib/constants' import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags' /** STEP 1: Your hosting provider diff --git a/src/view/com/auth/login/ForgotPasswordForm.tsx b/src/view/com/auth/login/ForgotPasswordForm.tsx index a794665c..215c393d 100644 --- a/src/view/com/auth/login/ForgotPasswordForm.tsx +++ b/src/view/com/auth/login/ForgotPasswordForm.tsx @@ -9,13 +9,13 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' +import {ComAtprotoServerDescribeServer} from '@atproto/api' import * as EmailValidator from 'email-validator' import {BskyAgent} from '@atproto/api' import {useAnalytics} from 'lib/analytics/analytics' import {Text} from '../../util/text/Text' import {s} from 'lib/styles' import {toNiceDomain} from 'lib/strings/url-helpers' -import {ServiceDescription} from 'state/models/session' import {isNetworkError} from 'lib/strings/errors' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' @@ -26,6 +26,8 @@ import {useLingui} from '@lingui/react' import {styles} from './styles' import {useModalControls} from '#/state/modals' +type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema + export const ForgotPasswordForm = ({ error, serviceUrl, diff --git a/src/view/com/auth/login/Login.tsx b/src/view/com/auth/login/Login.tsx index 27d08812..a2f924f9 100644 --- a/src/view/com/auth/login/Login.tsx +++ b/src/view/com/auth/login/Login.tsx @@ -2,8 +2,7 @@ import React, {useState, useEffect} from 'react' import {KeyboardAvoidingView} from 'react-native' import {useAnalytics} from 'lib/analytics/analytics' import {LoggedOutLayout} from 'view/com/util/layouts/LoggedOutLayout' -import {useStores, DEFAULT_SERVICE} from 'state/index' -import {ServiceDescription} from 'state/models/session' +import {DEFAULT_SERVICE} from '#/lib/constants' import {usePalette} from 'lib/hooks/usePalette' import {logger} from '#/logger' import {ChooseAccountForm} from './ChooseAccountForm' @@ -14,6 +13,7 @@ import {PasswordUpdatedForm} from './PasswordUpdatedForm' import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' import {useSession, SessionAccount} from '#/state/session' +import {useServiceQuery} from '#/state/queries/service' enum Forms { Login, @@ -25,20 +25,20 @@ enum Forms { export const Login = ({onPressBack}: {onPressBack: () => void}) => { const pal = usePalette('default') - const store = useStores() const {accounts} = useSession() const {track} = useAnalytics() const {_} = useLingui() const [error, setError] = useState('') - const [retryDescribeTrigger, setRetryDescribeTrigger] = useState({}) const [serviceUrl, setServiceUrl] = useState(DEFAULT_SERVICE) - const [serviceDescription, setServiceDescription] = useState< - ServiceDescription | undefined - >(undefined) const [initialHandle, setInitialHandle] = useState('') const [currentForm, setCurrentForm] = useState( accounts.length ? Forms.ChooseAccount : Forms.Login, ) + const { + data: serviceDescription, + error: serviceError, + refetch: refetchService, + } = useServiceQuery(serviceUrl) const onSelectAccount = (account?: SessionAccount) => { if (account?.service) { @@ -54,35 +54,21 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => { } useEffect(() => { - let aborted = false - setError('') - store.session.describeService(serviceUrl).then( - desc => { - if (aborted) { - return - } - setServiceDescription(desc) - }, - err => { - if (aborted) { - return - } - logger.warn(`Failed to fetch service description for ${serviceUrl}`, { - error: err, - }) - setError( - _( - msg`Unable to contact your service. Please check your Internet connection.`, - ), - ) - }, - ) - return () => { - aborted = true + if (serviceError) { + setError( + _( + msg`Unable to contact your service. Please check your Internet connection.`, + ), + ) + logger.warn(`Failed to fetch service description for ${serviceUrl}`, { + error: String(serviceError), + }) + } else { + setError('') } - }, [store.session, serviceUrl, retryDescribeTrigger, _]) + }, [serviceError, serviceUrl, _]) - const onPressRetryConnect = () => setRetryDescribeTrigger({}) + const onPressRetryConnect = () => refetchService() const onPressForgotPassword = () => { track('Signin:PressedForgotPassword') setCurrentForm(Forms.ForgotPassword) diff --git a/src/view/com/auth/login/LoginForm.tsx b/src/view/com/auth/login/LoginForm.tsx index be3a9513..365f2e25 100644 --- a/src/view/com/auth/login/LoginForm.tsx +++ b/src/view/com/auth/login/LoginForm.tsx @@ -10,12 +10,12 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' +import {ComAtprotoServerDescribeServer} from '@atproto/api' import {useAnalytics} from 'lib/analytics/analytics' import {Text} from '../../util/text/Text' import {s} from 'lib/styles' import {createFullHandle} from 'lib/strings/handles' import {toNiceDomain} from 'lib/strings/url-helpers' -import {ServiceDescription} from 'state/models/session' import {isNetworkError} from 'lib/strings/errors' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' @@ -27,6 +27,8 @@ import {styles} from './styles' import {useLingui} from '@lingui/react' import {useModalControls} from '#/state/modals' +type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema + export const LoginForm = ({ error, serviceUrl, diff --git a/src/view/com/auth/onboarding/RecommendedFeeds.tsx b/src/view/com/auth/onboarding/RecommendedFeeds.tsx index d134dae9..941671bf 100644 --- a/src/view/com/auth/onboarding/RecommendedFeeds.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeeds.tsx @@ -1,6 +1,5 @@ import React from 'react' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints' import {Text} from 'view/com/util/text/Text' @@ -16,9 +15,7 @@ import {useSuggestedFeedsQuery} from '#/state/queries/suggested-feeds' type Props = { next: () => void } -export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ - next, -}: Props) { +export function RecommendedFeeds({next}: Props) { const pal = usePalette('default') const {isTabletOrMobile} = useWebMediaQueries() const {isLoading, data} = useSuggestedFeedsQuery() @@ -146,7 +143,7 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ ) -}) +} const tdStyles = StyleSheet.create({ container: { diff --git a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx index 2eaf3cf2..7417e5b0 100644 --- a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx @@ -1,6 +1,5 @@ import React from 'react' import {View} from 'react-native' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {AppBskyFeedDefs, RichText as BskRichText} from '@atproto/api' import {Text} from 'view/com/util/text/Text' @@ -19,7 +18,7 @@ import { } from '#/state/queries/preferences' import {logger} from '#/logger' -export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ +export function RecommendedFeedsItem({ item, }: { item: AppBskyFeedDefs.GeneratorView @@ -164,4 +163,4 @@ export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ ) -}) +} diff --git a/src/view/com/auth/onboarding/RecommendedFollows.tsx b/src/view/com/auth/onboarding/RecommendedFollows.tsx index aa528cbd..c327a716 100644 --- a/src/view/com/auth/onboarding/RecommendedFollows.tsx +++ b/src/view/com/auth/onboarding/RecommendedFollows.tsx @@ -1,6 +1,5 @@ import React from 'react' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {AppBskyActorDefs, moderateProfile} from '@atproto/api' import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints' @@ -19,9 +18,7 @@ import {logger} from '#/logger' type Props = { next: () => void } -export const RecommendedFollows = observer(function RecommendedFollowsImpl({ - next, -}: Props) { +export function RecommendedFollows({next}: Props) { const pal = usePalette('default') const {isTabletOrMobile} = useWebMediaQueries() const {data: suggestedFollows, dataUpdatedAt} = useSuggestedFollowsQuery() @@ -211,7 +208,7 @@ export const RecommendedFollows = observer(function RecommendedFollowsImpl({ ) -}) +} const tdStyles = StyleSheet.create({ container: { diff --git a/src/view/com/auth/onboarding/WelcomeDesktop.tsx b/src/view/com/auth/onboarding/WelcomeDesktop.tsx index c066e9bd..1a30c17f 100644 --- a/src/view/com/auth/onboarding/WelcomeDesktop.tsx +++ b/src/view/com/auth/onboarding/WelcomeDesktop.tsx @@ -7,16 +7,13 @@ import {usePalette} from 'lib/hooks/usePalette' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout' import {Button} from 'view/com/util/forms/Button' -import {observer} from 'mobx-react-lite' type Props = { next: () => void skip: () => void } -export const WelcomeDesktop = observer(function WelcomeDesktopImpl({ - next, -}: Props) { +export function WelcomeDesktop({next}: Props) { const pal = usePalette('default') const horizontal = useMediaQuery({minWidth: 1300}) const title = ( @@ -105,7 +102,7 @@ export const WelcomeDesktop = observer(function WelcomeDesktopImpl({ ) -}) +} const styles = StyleSheet.create({ row: { diff --git a/src/view/com/auth/onboarding/WelcomeMobile.tsx b/src/view/com/auth/onboarding/WelcomeMobile.tsx index ef70a1fe..2717bff3 100644 --- a/src/view/com/auth/onboarding/WelcomeMobile.tsx +++ b/src/view/com/auth/onboarding/WelcomeMobile.tsx @@ -5,7 +5,6 @@ import {s} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Button} from 'view/com/util/forms/Button' -import {observer} from 'mobx-react-lite' import {ViewHeader} from 'view/com/util/ViewHeader' import {Trans} from '@lingui/macro' @@ -14,10 +13,7 @@ type Props = { skip: () => void } -export const WelcomeMobile = observer(function WelcomeMobileImpl({ - next, - skip, -}: Props) { +export function WelcomeMobile({next, skip}: Props) { const pal = usePalette('default') return ( @@ -102,7 +98,7 @@ export const WelcomeMobile = observer(function WelcomeMobileImpl({ /> ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/auth/withAuthRequired.tsx b/src/view/com/auth/withAuthRequired.tsx index 4b8b31d6..5fd89a8b 100644 --- a/src/view/com/auth/withAuthRequired.tsx +++ b/src/view/com/auth/withAuthRequired.tsx @@ -5,7 +5,6 @@ import { StyleSheet, TouchableOpacity, } from 'react-native' -import {observer} from 'mobx-react-lite' import {CenteredView} from '../util/Views' import {LoggedOut} from './LoggedOut' import {Onboarding} from './Onboarding' @@ -18,7 +17,7 @@ import {useSession} from '#/state/session' export const withAuthRequired =

( Component: React.ComponentType

, ): React.FC

=> - observer(function AuthRequired(props: P) { + function AuthRequired(props: P) { const {isInitialLoad, hasSession} = useSession() const onboardingState = useOnboardingState() if (isInitialLoad) { @@ -31,7 +30,7 @@ export const withAuthRequired =

( return } return - }) + } function Loading() { const pal = usePalette('default') diff --git a/src/view/com/composer/labels/LabelsBtn.tsx b/src/view/com/composer/labels/LabelsBtn.tsx index 7d90774f..a1068469 100644 --- a/src/view/com/composer/labels/LabelsBtn.tsx +++ b/src/view/com/composer/labels/LabelsBtn.tsx @@ -1,6 +1,5 @@ import React from 'react' import {Keyboard, StyleSheet} from 'react-native' -import {observer} from 'mobx-react-lite' import {Button} from 'view/com/util/forms/Button' import {usePalette} from 'lib/hooks/usePalette' import {ShieldExclamation} from 'lib/icons' @@ -11,7 +10,7 @@ import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' import {useModalControls} from '#/state/modals' -export const LabelsBtn = observer(function LabelsBtn({ +export function LabelsBtn({ labels, hasMedia, onChange, @@ -49,7 +48,7 @@ export const LabelsBtn = observer(function LabelsBtn({ ) : null} ) -}) +} const styles = StyleSheet.create({ button: { diff --git a/src/view/com/composer/select-language/SelectLangBtn.tsx b/src/view/com/composer/select-language/SelectLangBtn.tsx index 7ea63ed6..78b1e9ba 100644 --- a/src/view/com/composer/select-language/SelectLangBtn.tsx +++ b/src/view/com/composer/select-language/SelectLangBtn.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useMemo} from 'react' import {StyleSheet, Keyboard} from 'react-native' -import {observer} from 'mobx-react-lite' import { FontAwesomeIcon, FontAwesomeIconStyle, @@ -24,7 +23,7 @@ import { import {t, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -export const SelectLangBtn = observer(function SelectLangBtn() { +export function SelectLangBtn() { const pal = usePalette('default') const {_} = useLingui() const {openModal} = useModalControls() @@ -117,7 +116,7 @@ export const SelectLangBtn = observer(function SelectLangBtn() { )} ) -}) +} const styles = StyleSheet.create({ button: { diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx index bb54a204..c400aa48 100644 --- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx +++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx @@ -1,6 +1,5 @@ import React, {useEffect, useRef} from 'react' import {Animated, TouchableOpacity, StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' import {usePalette} from 'lib/hooks/usePalette' import {Text} from 'view/com/util/text/Text' @@ -10,7 +9,7 @@ import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {Trans} from '@lingui/macro' import {AppBskyActorDefs} from '@atproto/api' -export const Autocomplete = observer(function AutocompleteImpl({ +export function Autocomplete({ prefix, onSelect, }: { @@ -103,7 +102,7 @@ export const Autocomplete = observer(function AutocompleteImpl({ ) : null} ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/composer/useExternalLinkFetch.ts b/src/view/com/composer/useExternalLinkFetch.ts index 0c96e472..af604230 100644 --- a/src/view/com/composer/useExternalLinkFetch.ts +++ b/src/view/com/composer/useExternalLinkFetch.ts @@ -1,5 +1,4 @@ import {useState, useEffect} from 'react' -import {useStores} from 'state/index' import {ImageModel} from 'state/models/media/image' import * as apilib from 'lib/api/index' import {getLinkMeta} from 'lib/link-meta/link-meta' @@ -17,6 +16,7 @@ import { import {ComposerOpts} from 'state/shell/composer' import {POST_IMG_MAX} from 'lib/constants' import {logger} from '#/logger' +import {useSession} from '#/state/session' import {useGetPost} from '#/state/queries/post' export function useExternalLinkFetch({ @@ -24,7 +24,7 @@ export function useExternalLinkFetch({ }: { setQuote: (opts: ComposerOpts['quote']) => void }) { - const store = useStores() + const {agent} = useSession() const [extLink, setExtLink] = useState( undefined, ) @@ -56,7 +56,7 @@ export function useExternalLinkFetch({ }, ) } else if (isBskyCustomFeedUrl(extLink.uri)) { - getFeedAsEmbed(store, extLink.uri).then( + getFeedAsEmbed(agent, extLink.uri).then( ({embed, meta}) => { if (aborted) { return @@ -74,7 +74,7 @@ export function useExternalLinkFetch({ }, ) } else if (isBskyListUrl(extLink.uri)) { - getListAsEmbed(store, extLink.uri).then( + getListAsEmbed(agent, extLink.uri).then( ({embed, meta}) => { if (aborted) { return @@ -92,7 +92,7 @@ export function useExternalLinkFetch({ }, ) } else { - getLinkMeta(store, extLink.uri).then(meta => { + getLinkMeta(agent, extLink.uri).then(meta => { if (aborted) { return } @@ -134,7 +134,7 @@ export function useExternalLinkFetch({ }) } return cleanup - }, [store, extLink, setQuote, getPost]) + }, [agent, extLink, setQuote, getPost]) return {extLink, setExtLink} } diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx index 7c7ad061..ea740ec9 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx @@ -315,7 +315,6 @@ const ImageItem = ({ { {(!loaded || !imageDimensions) && } ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/modals/ChangeEmail.tsx b/src/view/com/modals/ChangeEmail.tsx index 6f7a9210..829188b5 100644 --- a/src/view/com/modals/ChangeEmail.tsx +++ b/src/view/com/modals/ChangeEmail.tsx @@ -1,7 +1,6 @@ import React, {useState} from 'react' import {ActivityIndicator, SafeAreaView, StyleSheet, View} from 'react-native' import {ScrollView, TextInput} from './util' -import {observer} from 'mobx-react-lite' import {Text} from '../util/text/Text' import {Button} from '../util/forms/Button' import {ErrorMessage} from '../util/error/ErrorMessage' @@ -24,7 +23,7 @@ enum Stages { export const snapPoints = ['90%'] -export const Component = observer(function Component({}: {}) { +export function Component() { const pal = usePalette('default') const {agent, currentAccount} = useSession() const {updateCurrentAccount} = useSessionApi() @@ -226,7 +225,7 @@ export const Component = observer(function Component({}: {}) { ) -}) +} const styles = StyleSheet.create({ titleSection: { diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx index 8dc3311f..8b42e1b1 100644 --- a/src/view/com/modals/ContentFilteringSettings.tsx +++ b/src/view/com/modals/ContentFilteringSettings.tsx @@ -2,7 +2,6 @@ import React from 'react' import {LabelPreference} from '@atproto/api' import {StyleSheet, Pressable, View} from 'react-native' import LinearGradient from 'react-native-linear-gradient' -import {observer} from 'mobx-react-lite' import {ScrollView} from './util' import {s, colors, gradients} from 'lib/styles' import {Text} from '../util/text/Text' @@ -28,82 +27,80 @@ import { export const snapPoints = ['90%'] -export const Component = observer( - function ContentFilteringSettingsImpl({}: {}) { - const {isMobile} = useWebMediaQueries() - const pal = usePalette('default') - const {_} = useLingui() - const {closeModal} = useModalControls() - const {data: preferences} = usePreferencesQuery() +export function Component({}: {}) { + const {isMobile} = useWebMediaQueries() + const pal = usePalette('default') + const {_} = useLingui() + const {closeModal} = useModalControls() + const {data: preferences} = usePreferencesQuery() - const onPressDone = React.useCallback(() => { - closeModal() - }, [closeModal]) + const onPressDone = React.useCallback(() => { + closeModal() + }, [closeModal]) - return ( - - - Content Filtering - + return ( + + + Content Filtering + - - - - - - - - - - - + + + + + + + + + + + - - - - - Done - - - - + + + + + Done + + + - ) - }, -) + + ) +} function AdultContentEnabledPref() { const pal = usePalette('default') @@ -171,7 +168,7 @@ function AdultContentEnabledPref() { } // TODO: Refactor this component to pass labels down to each tab -const ContentLabelPref = observer(function ContentLabelPrefImpl({ +function ContentLabelPref({ preferences, labelGroup, disabled, @@ -217,7 +214,7 @@ const ContentLabelPref = observer(function ContentLabelPrefImpl({ )} ) -}) +} interface SelectGroupProps { current: LabelPreference diff --git a/src/view/com/modals/InviteCodes.tsx b/src/view/com/modals/InviteCodes.tsx index 973c7c3a..82a826ac 100644 --- a/src/view/com/modals/InviteCodes.tsx +++ b/src/view/com/modals/InviteCodes.tsx @@ -5,7 +5,6 @@ import { View, ActivityIndicator, } from 'react-native' -import {observer} from 'mobx-react-lite' import {ComAtprotoServerDefs} from '@atproto/api' import { FontAwesomeIcon, @@ -129,7 +128,7 @@ export function Inner({invites}: {invites: InviteCodesQueryResponse}) { ) } -const InviteCode = observer(function InviteCodeImpl({ +function InviteCode({ testID, invite, used, @@ -211,7 +210,7 @@ const InviteCode = observer(function InviteCodeImpl({ ) : null} ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/modals/LinkWarning.tsx b/src/view/com/modals/LinkWarning.tsx index cb86387a..c63bc2a8 100644 --- a/src/view/com/modals/LinkWarning.tsx +++ b/src/view/com/modals/LinkWarning.tsx @@ -1,7 +1,6 @@ import React from 'react' import {Linking, SafeAreaView, StyleSheet, View} from 'react-native' import {ScrollView} from './util' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Text} from '../util/text/Text' import {Button} from '../util/forms/Button' @@ -16,13 +15,7 @@ import {useModalControls} from '#/state/modals' export const snapPoints = ['50%'] -export const Component = observer(function Component({ - text, - href, -}: { - text: string - href: string -}) { +export function Component({text, href}: {text: string; href: string}) { const pal = usePalette('default') const {closeModal} = useModalControls() const {isMobile} = useWebMediaQueries() @@ -97,7 +90,7 @@ export const Component = observer(function Component({ ) -}) +} function LinkBox({href}: {href: string}) { const pal = usePalette('default') diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 38c8bc7b..d834db40 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -1,7 +1,6 @@ import React, {useRef, useEffect} from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context' -import {observer} from 'mobx-react-lite' import BottomSheet from '@gorhom/bottom-sheet' import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' import {usePalette} from 'lib/hooks/usePalette' @@ -40,7 +39,7 @@ import * as LinkWarningModal from './LinkWarning' const DEFAULT_SNAPPOINTS = ['90%'] const HANDLE_HEIGHT = 24 -export const ModalsContainer = observer(function ModalsContainer() { +export function ModalsContainer() { const {isModalActive, activeModals} = useModals() const {closeModal} = useModalControls() const bottomSheetRef = useRef(null) @@ -198,7 +197,7 @@ export const ModalsContainer = observer(function ModalsContainer() { {element} ) -}) +} const styles = StyleSheet.create({ handle: { diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 28f6c36c..74aa7b1a 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -1,6 +1,5 @@ import React from 'react' import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import type {Modal as ModalIface} from '#/state/modals' @@ -33,7 +32,7 @@ import * as VerifyEmailModal from './VerifyEmail' import * as ChangeEmailModal from './ChangeEmail' import * as LinkWarningModal from './LinkWarning' -export const ModalsContainer = observer(function ModalsContainer() { +export function ModalsContainer() { const {isModalActive, activeModals} = useModals() if (!isModalActive) { @@ -47,7 +46,7 @@ export const ModalsContainer = observer(function ModalsContainer() { ))} ) -}) +} function Modal({modal}: {modal: ModalIface}) { const {isModalActive} = useModals() diff --git a/src/view/com/modals/SelfLabel.tsx b/src/view/com/modals/SelfLabel.tsx index f8b64085..092dd2d3 100644 --- a/src/view/com/modals/SelfLabel.tsx +++ b/src/view/com/modals/SelfLabel.tsx @@ -1,6 +1,5 @@ import React, {useState} from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {Text} from '../util/text/Text' import {s, colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' @@ -17,7 +16,7 @@ const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] export const snapPoints = ['50%'] -export const Component = observer(function Component({ +export function Component({ labels, hasMedia, onChange, @@ -161,7 +160,7 @@ export const Component = observer(function Component({ ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx index 09a46031..b3029385 100644 --- a/src/view/com/modals/ServerInput.tsx +++ b/src/view/com/modals/ServerInput.tsx @@ -9,7 +9,7 @@ import {Text} from '../util/text/Text' import {s, colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {useTheme} from 'lib/ThemeContext' -import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'state/index' +import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'lib/constants' import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/src/view/com/modals/VerifyEmail.tsx b/src/view/com/modals/VerifyEmail.tsx index 106e05b8..e7a4537c 100644 --- a/src/view/com/modals/VerifyEmail.tsx +++ b/src/view/com/modals/VerifyEmail.tsx @@ -8,7 +8,6 @@ import { } from 'react-native' import {Svg, Circle, Path} from 'react-native-svg' import {ScrollView, TextInput} from './util' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Text} from '../util/text/Text' import {Button} from '../util/forms/Button' @@ -32,11 +31,7 @@ enum Stages { ConfirmCode, } -export const Component = observer(function Component({ - showReminder, -}: { - showReminder?: boolean -}) { +export function Component({showReminder}: {showReminder?: boolean}) { const pal = usePalette('default') const {agent, currentAccount} = useSession() const {updateCurrentAccount} = useSessionApi() @@ -244,7 +239,7 @@ export const Component = observer(function Component({ ) -}) +} function ReminderIllustration() { const pal = usePalette('default') diff --git a/src/view/com/modals/lang-settings/LanguageToggle.tsx b/src/view/com/modals/lang-settings/LanguageToggle.tsx index 86e38a4d..45b100f2 100644 --- a/src/view/com/modals/lang-settings/LanguageToggle.tsx +++ b/src/view/com/modals/lang-settings/LanguageToggle.tsx @@ -1,11 +1,10 @@ import React from 'react' import {StyleSheet} from 'react-native' import {usePalette} from 'lib/hooks/usePalette' -import {observer} from 'mobx-react-lite' import {ToggleButton} from 'view/com/util/forms/ToggleButton' import {useLanguagePrefs, toPostLanguages} from '#/state/preferences/languages' -export const LanguageToggle = observer(function LanguageToggleImpl({ +export function LanguageToggle({ code2, name, onPress, @@ -39,7 +38,7 @@ export const LanguageToggle = observer(function LanguageToggleImpl({ style={[pal.border, styles.languageToggle, isDisabled && styles.dimmed]} /> ) -}) +} const styles = StyleSheet.create({ languageToggle: { diff --git a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx index 7fcb24d5..05cfb811 100644 --- a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx +++ b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx @@ -1,6 +1,5 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {ScrollView} from '../util' import {Text} from '../../util/text/Text' import {usePalette} from 'lib/hooks/usePalette' @@ -19,7 +18,7 @@ import { export const snapPoints = ['100%'] -export const Component = observer(function PostLanguagesSettingsImpl() { +export function Component() { const {closeModal} = useModalControls() const langPrefs = useLanguagePrefs() const setLangPrefs = useLanguagePrefsApi() @@ -111,7 +110,7 @@ export const Component = observer(function PostLanguagesSettingsImpl() { ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/modals/report/Modal.tsx b/src/view/com/modals/report/Modal.tsx index 5183bdc1..06075547 100644 --- a/src/view/com/modals/report/Modal.tsx +++ b/src/view/com/modals/report/Modal.tsx @@ -2,7 +2,6 @@ import React, {useState, useMemo} from 'react' import {Linking, StyleSheet, TouchableOpacity, View} from 'react-native' import {ScrollView} from 'react-native-gesture-handler' import {AtUri} from '@atproto/api' -import {useStores} from 'state/index' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {s} from 'lib/styles' import {Text} from '../../util/text/Text' @@ -17,6 +16,7 @@ import {CollectionId} from './types' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useModalControls} from '#/state/modals' +import {useSession} from '#/state/session' const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright' @@ -39,7 +39,7 @@ type ReportComponentProps = } export function Component(content: ReportComponentProps) { - const store = useStores() + const {agent} = useSession() const {closeModal} = useModalControls() const pal = usePalette('default') const {isMobile} = useWebMediaQueries() @@ -70,7 +70,7 @@ export function Component(content: ReportComponentProps) { const $type = !isAccountReport ? 'com.atproto.repo.strongRef' : 'com.atproto.admin.defs#repoRef' - await store.agent.createModerationReport({ + await agent.createModerationReport({ reasonType: issue, subject: { $type, diff --git a/src/view/com/pager/FeedsTabBar.web.tsx b/src/view/com/pager/FeedsTabBar.web.tsx index b48690a6..29837289 100644 --- a/src/view/com/pager/FeedsTabBar.web.tsx +++ b/src/view/com/pager/FeedsTabBar.web.tsx @@ -1,7 +1,6 @@ import React from 'react' import {StyleSheet} from 'react-native' import Animated from 'react-native-reanimated' -import {observer} from 'mobx-react-lite' import {TabBar} from 'view/com/pager/TabBar' import {RenderTabBarFnProps} from 'view/com/pager/Pager' import {usePalette} from 'lib/hooks/usePalette' @@ -11,7 +10,7 @@ import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import {useShellLayout} from '#/state/shell/shell-layout' import {usePinnedFeedsInfos} from '#/state/queries/feed' -export const FeedsTabBar = observer(function FeedsTabBarImpl( +export function FeedsTabBar( props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, ) { const {isMobile, isTablet} = useWebMediaQueries() @@ -22,9 +21,9 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl( } else { return null } -}) +} -const FeedsTabBarTablet = observer(function FeedsTabBarTabletImpl( +function FeedsTabBarTablet( props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, ) { const feeds = usePinnedFeedsInfos() @@ -48,7 +47,7 @@ const FeedsTabBarTablet = observer(function FeedsTabBarTabletImpl( /> ) -}) +} const styles = StyleSheet.create({ tabBar: { diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx index de985fb7..e0c0be1e 100644 --- a/src/view/com/pager/FeedsTabBarMobile.tsx +++ b/src/view/com/pager/FeedsTabBarMobile.tsx @@ -1,6 +1,5 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {TabBar} from 'view/com/pager/TabBar' import {RenderTabBarFnProps} from 'view/com/pager/Pager' import {usePalette} from 'lib/hooks/usePalette' @@ -20,7 +19,7 @@ import {useShellLayout} from '#/state/shell/shell-layout' import {useSession} from '#/state/session' import {usePinnedFeedsInfos} from '#/state/queries/feed' -export const FeedsTabBar = observer(function FeedsTabBarImpl( +export function FeedsTabBar( props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, ) { const pal = usePalette('default') @@ -88,7 +87,7 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl( /> ) -}) +} const styles = StyleSheet.create({ tabBar: { diff --git a/src/view/com/posts/FeedSlice.tsx b/src/view/com/posts/FeedSlice.tsx index fad9f9b4..99ee3853 100644 --- a/src/view/com/posts/FeedSlice.tsx +++ b/src/view/com/posts/FeedSlice.tsx @@ -1,6 +1,5 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {FeedPostSlice} from '#/state/queries/post-feed' import {AtUri, moderatePost, ModerationOpts} from '@atproto/api' import {Link} from '../util/Link' @@ -10,7 +9,7 @@ import {FeedItem} from './FeedItem' import {usePalette} from 'lib/hooks/usePalette' import {makeProfileLink} from 'lib/routes/links' -export const FeedSlice = observer(function FeedSliceImpl({ +export function FeedSlice({ slice, dataUpdatedAt, ignoreFilterFor, @@ -94,7 +93,7 @@ export const FeedSlice = observer(function FeedSliceImpl({ ))} ) -}) +} function ViewFullThread({slice}: {slice: FeedPostSlice}) { const pal = usePalette('default') diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 2f359018..cd985545 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -1,6 +1,5 @@ import * as React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' -import {observer} from 'mobx-react-lite' import { AppBskyActorDefs, moderateProfile, @@ -152,7 +151,7 @@ function ProfileCardPills({ ) } -const FollowersList = observer(function FollowersListImpl({ +function FollowersList({ followers, }: { followers?: AppBskyActorDefs.ProfileView[] | undefined @@ -196,7 +195,7 @@ const FollowersList = observer(function FollowersListImpl({ ))} ) -}) +} export function ProfileCardWithFollowBtn({ profile, diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index ef128e87..0e245f0f 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -1,6 +1,5 @@ import React from 'react' import {Pressable, StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {useNavigation} from '@react-navigation/native' import {usePalette} from 'lib/hooks/usePalette' @@ -12,7 +11,6 @@ import {LoadingPlaceholder} from '../util/LoadingPlaceholder' import {CenteredView} from '../util/Views' import {sanitizeHandle} from 'lib/strings/handles' import {makeProfileLink} from 'lib/routes/links' -import {useStores} from 'state/index' import {NavigationProp} from 'lib/routes/types' import {BACK_HITSLOP} from 'lib/constants' import {isNative} from 'platform/detection' @@ -22,7 +20,7 @@ import {msg} from '@lingui/macro' import {useSetDrawerOpen} from '#/state/shell' import {emitSoftReset} from '#/state/events' -export const ProfileSubpageHeader = observer(function HeaderImpl({ +export function ProfileSubpageHeader({ isLoading, href, title, @@ -45,7 +43,6 @@ export const ProfileSubpageHeader = observer(function HeaderImpl({ | undefined avatarType: UserAvatarType }>) { - const store = useStores() const setDrawerOpen = useSetDrawerOpen() const navigation = useNavigation() const {_} = useLingui() @@ -183,7 +180,7 @@ export const ProfileSubpageHeader = observer(function HeaderImpl({ ) -}) +} const styles = StyleSheet.create({ backBtn: { diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index c5e438f8..fa5f12f6 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -6,7 +6,6 @@ import {niceDate} from 'lib/strings/time' import {usePalette} from 'lib/hooks/usePalette' import {TypographyVariant} from 'lib/ThemeContext' import {UserAvatar} from './UserAvatar' -import {observer} from 'mobx-react-lite' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {isAndroid} from 'platform/detection' @@ -30,7 +29,7 @@ interface PostMetaOpts { style?: StyleProp } -export const PostMeta = observer(function PostMetaImpl(opts: PostMetaOpts) { +export function PostMeta(opts: PostMetaOpts) { const pal = usePalette('default') const displayName = opts.author.displayName || opts.author.handle const handle = opts.author.handle @@ -92,7 +91,7 @@ export const PostMeta = observer(function PostMetaImpl(opts: PostMetaOpts) { ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/util/SimpleViewHeader.tsx b/src/view/com/util/SimpleViewHeader.tsx index c871d940..e86e3756 100644 --- a/src/view/com/util/SimpleViewHeader.tsx +++ b/src/view/com/util/SimpleViewHeader.tsx @@ -1,5 +1,4 @@ import React from 'react' -import {observer} from 'mobx-react-lite' import { StyleProp, StyleSheet, @@ -18,7 +17,7 @@ import {useSetDrawerOpen} from '#/state/shell' const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} -export const SimpleViewHeader = observer(function SimpleViewHeaderImpl({ +export function SimpleViewHeader({ showBackButton = true, style, children, @@ -76,7 +75,7 @@ export const SimpleViewHeader = observer(function SimpleViewHeaderImpl({ {children} ) -}) +} const styles = StyleSheet.create({ header: { diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index adf2e4f0..082cae59 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -1,5 +1,4 @@ import React from 'react' -import {observer} from 'mobx-react-lite' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {useNavigation} from '@react-navigation/native' @@ -15,7 +14,7 @@ import {useSetDrawerOpen} from '#/state/shell' const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} -export const ViewHeader = observer(function ViewHeaderImpl({ +export function ViewHeader({ title, canGoBack, showBackButton = true, @@ -108,7 +107,7 @@ export const ViewHeader = observer(function ViewHeaderImpl({ ) } -}) +} function DesktopWebHeader({ title, @@ -140,7 +139,7 @@ function DesktopWebHeader({ ) } -const Container = observer(function ContainerImpl({ +function Container({ children, hideOnScroll, showBorder, @@ -178,7 +177,7 @@ const Container = observer(function ContainerImpl({ {children} ) -}) +} const styles = StyleSheet.create({ header: { diff --git a/src/view/com/util/fab/FABInner.tsx b/src/view/com/util/fab/FABInner.tsx index 5b1d5d88..9787d92f 100644 --- a/src/view/com/util/fab/FABInner.tsx +++ b/src/view/com/util/fab/FABInner.tsx @@ -1,5 +1,4 @@ import React, {ComponentProps} from 'react' -import {observer} from 'mobx-react-lite' import {StyleSheet, TouchableWithoutFeedback} from 'react-native' import LinearGradient from 'react-native-linear-gradient' import {gradients} from 'lib/styles' @@ -15,11 +14,7 @@ export interface FABProps icon: JSX.Element } -export const FABInner = observer(function FABInnerImpl({ - testID, - icon, - ...props -}: FABProps) { +export function FABInner({testID, icon, ...props}: FABProps) { const insets = useSafeAreaInsets() const {isMobile, isTablet} = useWebMediaQueries() const {fabMinimalShellTransform} = useMinimalShellMode() @@ -55,7 +50,7 @@ export const FABInner = observer(function FABInnerImpl({ ) -}) +} const styles = StyleSheet.create({ sizeRegular: { diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index 6cbcddc3..b5b6c1b5 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -2,8 +2,8 @@ import React from 'react' import {StyleProp, StyleSheet, Pressable, View, ViewStyle} from 'react-native' import {Image} from 'expo-image' import {clamp} from 'lib/numbers' -import {useStores} from 'state/index' import {Dimensions} from 'lib/media/types' +import * as imageSizes from 'lib/media/image-sizes' const MIN_ASPECT_RATIO = 0.33 // 1/3 const MAX_ASPECT_RATIO = 5 // 5/1 @@ -29,9 +29,8 @@ export function AutoSizedImage({ style, children = null, }: Props) { - const store = useStores() const [dim, setDim] = React.useState( - dimensionsHint || store.imageSizes.get(uri), + dimensionsHint || imageSizes.get(uri), ) const [aspectRatio, setAspectRatio] = React.useState( dim ? calc(dim) : 1, @@ -41,14 +40,14 @@ export function AutoSizedImage({ if (dim) { return } - store.imageSizes.fetch(uri).then(newDim => { + imageSizes.fetch(uri).then(newDim => { if (aborted) { return } setDim(newDim) setAspectRatio(calc(newDim)) }) - }, [dim, setDim, setAspectRatio, store, uri]) + }, [dim, setDim, setAspectRatio, uri]) if (onPress || onLongPress || onPressIn) { return ( diff --git a/src/view/com/util/load-latest/LoadLatestBtn.tsx b/src/view/com/util/load-latest/LoadLatestBtn.tsx index f9a9387b..970d3a73 100644 --- a/src/view/com/util/load-latest/LoadLatestBtn.tsx +++ b/src/view/com/util/load-latest/LoadLatestBtn.tsx @@ -1,6 +1,5 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' @@ -12,7 +11,7 @@ const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity) import {isWeb} from 'platform/detection' -export const LoadLatestBtn = observer(function LoadLatestBtnImpl({ +export function LoadLatestBtn({ onPress, label, showIndicator, @@ -44,7 +43,7 @@ export const LoadLatestBtn = observer(function LoadLatestBtnImpl({ {showIndicator && } ) -}) +} const styles = StyleSheet.create({ loadLatest: { diff --git a/src/view/com/util/post-embeds/ListEmbed.tsx b/src/view/com/util/post-embeds/ListEmbed.tsx index dbf35003..fc5ad270 100644 --- a/src/view/com/util/post-embeds/ListEmbed.tsx +++ b/src/view/com/util/post-embeds/ListEmbed.tsx @@ -1,12 +1,11 @@ import React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {usePalette} from 'lib/hooks/usePalette' -import {observer} from 'mobx-react-lite' import {ListCard} from 'view/com/lists/ListCard' import {AppBskyGraphDefs} from '@atproto/api' import {s} from 'lib/styles' -export const ListEmbed = observer(function ListEmbedImpl({ +export function ListEmbed({ item, style, }: { @@ -20,7 +19,7 @@ export const ListEmbed = observer(function ListEmbedImpl({ ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 8c80b0fd..50db7e81 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,6 +1,5 @@ import React from 'react' import {useFocusEffect} from '@react-navigation/native' -import {observer} from 'mobx-react-lite' import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types' import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed' import {withAuthRequired} from 'view/com/auth/withAuthRequired' @@ -15,130 +14,126 @@ import {usePreferencesQuery} from '#/state/queries/preferences' import {emitSoftReset} from '#/state/events' type Props = NativeStackScreenProps -export const HomeScreen = withAuthRequired( - observer(function HomeScreenImpl({}: Props) { - const setMinimalShellMode = useSetMinimalShellMode() - const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() - const pagerRef = React.useRef(null) - const [selectedPage, setSelectedPage] = React.useState(0) - const [customFeeds, setCustomFeeds] = React.useState([]) - const {data: preferences} = usePreferencesQuery() +export const HomeScreen = withAuthRequired(function HomeScreenImpl({}: Props) { + const setMinimalShellMode = useSetMinimalShellMode() + const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() + const pagerRef = React.useRef(null) + const [selectedPage, setSelectedPage] = React.useState(0) + const [customFeeds, setCustomFeeds] = React.useState([]) + const {data: preferences} = usePreferencesQuery() - React.useEffect(() => { - if (!preferences?.feeds?.pinned) return + React.useEffect(() => { + if (!preferences?.feeds?.pinned) return - const pinned = preferences.feeds.pinned + const pinned = preferences.feeds.pinned - const feeds: FeedDescriptor[] = [] + const feeds: FeedDescriptor[] = [] - for (const uri of pinned) { - if (uri.includes('app.bsky.feed.generator')) { - feeds.push(`feedgen|${uri}`) - } else if (uri.includes('app.bsky.graph.list')) { - feeds.push(`list|${uri}`) - } + for (const uri of pinned) { + if (uri.includes('app.bsky.feed.generator')) { + feeds.push(`feedgen|${uri}`) + } else if (uri.includes('app.bsky.graph.list')) { + feeds.push(`list|${uri}`) } + } - setCustomFeeds(feeds) + setCustomFeeds(feeds) - pagerRef.current?.setPage(0) - }, [preferences?.feeds?.pinned, setCustomFeeds, pagerRef]) + pagerRef.current?.setPage(0) + }, [preferences?.feeds?.pinned, setCustomFeeds, pagerRef]) - const homeFeedParams = React.useMemo(() => { - if (!preferences) return {} + const homeFeedParams = React.useMemo(() => { + if (!preferences) return {} - return { - mergeFeedEnabled: Boolean( - preferences.feedViewPrefs.lab_mergeFeedEnabled, - ), - mergeFeedSources: preferences.feeds.saved, + return { + mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled), + mergeFeedSources: preferences.feeds.saved, + } + }, [preferences]) + + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + setDrawerSwipeDisabled(selectedPage > 0) + return () => { + setDrawerSwipeDisabled(false) } - }, [preferences]) + }, [setDrawerSwipeDisabled, selectedPage, setMinimalShellMode]), + ) - useFocusEffect( - React.useCallback(() => { + const onPageSelected = React.useCallback( + (index: number) => { + setMinimalShellMode(false) + setSelectedPage(index) + setDrawerSwipeDisabled(index > 0) + }, + [setDrawerSwipeDisabled, setSelectedPage, setMinimalShellMode], + ) + + const onPressSelected = React.useCallback(() => { + emitSoftReset() + }, []) + + const onPageScrollStateChanged = React.useCallback( + (state: 'idle' | 'dragging' | 'settling') => { + if (state === 'dragging') { setMinimalShellMode(false) - setDrawerSwipeDisabled(selectedPage > 0) - return () => { - setDrawerSwipeDisabled(false) - } - }, [setDrawerSwipeDisabled, selectedPage, setMinimalShellMode]), - ) + } + }, + [setMinimalShellMode], + ) - const onPageSelected = React.useCallback( - (index: number) => { - setMinimalShellMode(false) - setSelectedPage(index) - setDrawerSwipeDisabled(index > 0) - }, - [setDrawerSwipeDisabled, setSelectedPage, setMinimalShellMode], - ) + const renderTabBar = React.useCallback( + (props: RenderTabBarFnProps) => { + return ( + + ) + }, + [onPressSelected], + ) - const onPressSelected = React.useCallback(() => { - emitSoftReset() - }, []) + const renderFollowingEmptyState = React.useCallback(() => { + return + }, []) - const onPageScrollStateChanged = React.useCallback( - (state: 'idle' | 'dragging' | 'settling') => { - if (state === 'dragging') { - setMinimalShellMode(false) - } - }, - [setMinimalShellMode], - ) + const renderCustomFeedEmptyState = React.useCallback(() => { + return + }, []) - const renderTabBar = React.useCallback( - (props: RenderTabBarFnProps) => { + return ( + + + {customFeeds.map((f, index) => { return ( - ) - }, - [onPressSelected], - ) - - const renderFollowingEmptyState = React.useCallback(() => { - return - }, []) - - const renderCustomFeedEmptyState = React.useCallback(() => { - return - }, []) - - return ( - - - {customFeeds.map((f, index) => { - return ( - - ) - })} - - ) - }), -) + })} + + ) +}) diff --git a/src/view/screens/LanguageSettings.tsx b/src/view/screens/LanguageSettings.tsx index 67745152..649daea0 100644 --- a/src/view/screens/LanguageSettings.tsx +++ b/src/view/screens/LanguageSettings.tsx @@ -1,6 +1,5 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {Text} from '../com/util/text/Text' import {s} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' @@ -23,9 +22,7 @@ import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' type Props = NativeStackScreenProps -export const LanguageSettingsScreen = observer(function LanguageSettingsImpl( - _: Props, -) { +export function LanguageSettingsScreen(_: Props) { const pal = usePalette('default') const langPrefs = useLanguagePrefs() const setLangPrefs = useLanguagePrefsApi() @@ -192,7 +189,7 @@ export const LanguageSettingsScreen = observer(function LanguageSettingsImpl( ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/Log.tsx b/src/view/screens/Log.tsx index 69c07eda..8680b851 100644 --- a/src/view/screens/Log.tsx +++ b/src/view/screens/Log.tsx @@ -1,7 +1,6 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ScrollView} from '../com/util/Views' @@ -15,7 +14,7 @@ import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' import {useSetMinimalShellMode} from '#/state/shell' -export const LogScreen = observer(function Log({}: NativeStackScreenProps< +export function LogScreen({}: NativeStackScreenProps< CommonNavigatorParams, 'Log' >) { @@ -88,7 +87,7 @@ export const LogScreen = observer(function Log({}: NativeStackScreenProps< ) -}) +} const styles = StyleSheet.create({ entry: { diff --git a/src/view/screens/Moderation.tsx b/src/view/screens/Moderation.tsx index eb952c06..37eecf22 100644 --- a/src/view/screens/Moderation.tsx +++ b/src/view/screens/Moderation.tsx @@ -5,7 +5,6 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -import {observer} from 'mobx-react-lite' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {s} from 'lib/styles' @@ -21,7 +20,7 @@ import {useModalControls} from '#/state/modals' type Props = NativeStackScreenProps export const ModerationScreen = withAuthRequired( - observer(function Moderation({}: Props) { + function Moderation({}: Props) { const pal = usePalette('default') const setMinimalShellMode = useSetMinimalShellMode() const {screen, track} = useAnalytics() @@ -111,7 +110,7 @@ export const ModerationScreen = withAuthRequired( ) - }), + }, ) const styles = StyleSheet.create({ diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index 752f78dc..844a96d1 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -3,7 +3,6 @@ import {StyleSheet, View} from 'react-native' import Animated from 'react-native-reanimated' import {useFocusEffect} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' -import {observer} from 'mobx-react-lite' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {makeRecordUri} from 'lib/strings/url-helpers' import {withAuthRequired} from 'view/com/auth/withAuthRequired' @@ -26,83 +25,83 @@ import {CenteredView} from '../com/util/Views' import {useComposerControls} from '#/state/shell/composer' type Props = NativeStackScreenProps -export const PostThreadScreen = withAuthRequired( - observer(function PostThreadScreenImpl({route}: Props) { - const queryClient = useQueryClient() - const {fabMinimalShellTransform} = useMinimalShellMode() - const setMinimalShellMode = useSetMinimalShellMode() - const {openComposer} = useComposerControls() - const safeAreaInsets = useSafeAreaInsets() - const {name, rkey} = route.params - const {isMobile} = useWebMediaQueries() - const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - const {data: resolvedUri, error: uriError} = useResolveUriQuery(uri) +export const PostThreadScreen = withAuthRequired(function PostThreadScreenImpl({ + route, +}: Props) { + const queryClient = useQueryClient() + const {fabMinimalShellTransform} = useMinimalShellMode() + const setMinimalShellMode = useSetMinimalShellMode() + const {openComposer} = useComposerControls() + const safeAreaInsets = useSafeAreaInsets() + const {name, rkey} = route.params + const {isMobile} = useWebMediaQueries() + const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) + const {data: resolvedUri, error: uriError} = useResolveUriQuery(uri) - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) + + const onPressReply = React.useCallback(() => { + if (!resolvedUri) { + return + } + const thread = queryClient.getQueryData( + POST_THREAD_RQKEY(resolvedUri.uri), ) - - const onPressReply = React.useCallback(() => { - if (!resolvedUri) { - return - } - const thread = queryClient.getQueryData( - POST_THREAD_RQKEY(resolvedUri.uri), - ) - if (thread?.type !== 'post') { - return - } - openComposer({ - replyTo: { - uri: thread.post.uri, - cid: thread.post.cid, - text: thread.record.text, - author: { - handle: thread.post.author.handle, - displayName: thread.post.author.displayName, - avatar: thread.post.author.avatar, - }, + if (thread?.type !== 'post') { + return + } + openComposer({ + replyTo: { + uri: thread.post.uri, + cid: thread.post.cid, + text: thread.record.text, + author: { + handle: thread.post.author.handle, + displayName: thread.post.author.displayName, + avatar: thread.post.author.avatar, }, - onPost: () => - queryClient.invalidateQueries({ - queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''), - }), - }) - }, [openComposer, queryClient, resolvedUri]) + }, + onPost: () => + queryClient.invalidateQueries({ + queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''), + }), + }) + }, [openComposer, queryClient, resolvedUri]) - return ( - - {isMobile && } - - {uriError ? ( - - - - ) : ( - - )} - - {isMobile && ( - - - + return ( + + {isMobile && } + + {uriError ? ( + + + + ) : ( + )} - ) - }), -) + {isMobile && ( + + + + )} + + ) +}) const styles = StyleSheet.create({ prompt: { diff --git a/src/view/screens/PreferencesHomeFeed.tsx b/src/view/screens/PreferencesHomeFeed.tsx index 7b240ded..2fd0eff3 100644 --- a/src/view/screens/PreferencesHomeFeed.tsx +++ b/src/view/screens/PreferencesHomeFeed.tsx @@ -1,6 +1,5 @@ import React, {useState} from 'react' import {ScrollView, StyleSheet, TouchableOpacity, View} from 'react-native' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Slider} from '@miblanchard/react-native-slider' import {Text} from '../com/util/text/Text' @@ -72,9 +71,7 @@ type Props = NativeStackScreenProps< CommonNavigatorParams, 'PreferencesHomeFeed' > -export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({ - navigation, -}: Props) { +export function PreferencesHomeFeed({navigation}: Props) { const pal = usePalette('default') const {_} = useLingui() const {isTabletOrDesktop} = useWebMediaQueries() @@ -308,7 +305,7 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({ ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/PreferencesThreads.tsx b/src/view/screens/PreferencesThreads.tsx index 2386f644..7bd87b71 100644 --- a/src/view/screens/PreferencesThreads.tsx +++ b/src/view/screens/PreferencesThreads.tsx @@ -6,7 +6,6 @@ import { TouchableOpacity, View, } from 'react-native' -import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Text} from '../com/util/text/Text' import {s, colors} from 'lib/styles' @@ -25,9 +24,7 @@ import { } from '#/state/queries/preferences' type Props = NativeStackScreenProps -export const PreferencesThreads = observer(function PreferencesThreadsImpl({ - navigation, -}: Props) { +export function PreferencesThreads({navigation}: Props) { const pal = usePalette('default') const {_} = useLingui() const {isTabletOrDesktop} = useWebMediaQueries() @@ -162,7 +159,7 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({ ) -}) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index f62790be..62f5f1b3 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -15,7 +15,6 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {CommonNavigatorParams} from 'lib/routes/types' import {makeRecordUri} from 'lib/strings/url-helpers' import {colors, s} from 'lib/styles' -import {observer} from 'mobx-react-lite' import {FeedDescriptor} from '#/state/queries/post-feed' import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' @@ -71,7 +70,7 @@ interface SectionRef { type Props = NativeStackScreenProps export const ProfileFeedScreen = withAuthRequired( - observer(function ProfileFeedScreenImpl(props: Props) { + function ProfileFeedScreenImpl(props: Props) { const {rkey, name: handleOrDid} = props.route.params const pal = usePalette('default') @@ -129,7 +128,7 @@ export const ProfileFeedScreen = withAuthRequired( ) - }), + }, ) function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) { @@ -154,7 +153,7 @@ function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) { ) } -export const ProfileFeedScreenInner = function ProfileFeedScreenInnerImpl({ +export function ProfileFeedScreenInner({ preferences, feedInfo, }: { @@ -485,7 +484,7 @@ const FeedSection = React.forwardRef( }, ) -const AboutSection = observer(function AboutPageImpl({ +function AboutSection({ feedOwnerDid, feedRkey, feedInfo, @@ -606,7 +605,7 @@ const AboutSection = observer(function AboutPageImpl({ ) -}) +} const styles = StyleSheet.create({ btn: { diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx index c7abcf09..e3e50ca2 100644 --- a/src/view/screens/SavedFeeds.tsx +++ b/src/view/screens/SavedFeeds.tsx @@ -14,7 +14,6 @@ import {track} from '#/lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' import {CommonNavigatorParams} from 'lib/routes/types' -import {observer} from 'mobx-react-lite' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from 'view/com/util/ViewHeader' @@ -146,7 +145,7 @@ export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) { ) }) -const ListItem = observer(function ListItemImpl({ +function ListItem({ feedUri, isPinned, }: { @@ -269,7 +268,7 @@ const ListItem = observer(function ListItemImpl({ ) -}) +} const styles = StyleSheet.create({ desktopContainer: { diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 3f7ef146..916fd2a5 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -19,11 +19,9 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -import {observer} from 'mobx-react-lite' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {withAuthRequired} from 'view/com/auth/withAuthRequired' import * as AppInfo from 'lib/app-info' -import {useStores} from 'state/index' import {s, colors} from 'lib/styles' import {ScrollView} from '../com/util/Views' import {ViewHeader} from '../com/util/ViewHeader' @@ -45,7 +43,7 @@ import {formatCount} from 'view/com/util/numeric/format' import Clipboard from '@react-native-clipboard/clipboard' import {makeProfileLink} from 'lib/routes/links' import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn' -import {logger} from '#/logger' +import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile' import {useModalControls} from '#/state/modals' import { useSetMinimalShellMode, @@ -69,6 +67,7 @@ import {useDebugHeaderSetting} from 'lib/api/debug-appview-proxy-header' import {STATUS_PAGE_URL} from 'lib/constants' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' function SettingsAccountCard({account}: {account: SessionAccount}) { const pal = usePalette('default') @@ -135,630 +134,620 @@ function SettingsAccountCard({account}: {account: SessionAccount}) { } type Props = NativeStackScreenProps -export const SettingsScreen = withAuthRequired( - observer(function Settings({}: Props) { - const colorMode = useColorMode() - const setColorMode = useSetColorMode() - const pal = usePalette('default') - const store = useStores() - const {_} = useLingui() - const setMinimalShellMode = useSetMinimalShellMode() - const requireAltTextEnabled = useRequireAltTextEnabled() - const setRequireAltTextEnabled = useSetRequireAltTextEnabled() - const onboardingDispatch = useOnboardingDispatch() - const navigation = useNavigation() - const {isMobile} = useWebMediaQueries() - const {screen, track} = useAnalytics() - const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting( - store.agent, +export const SettingsScreen = withAuthRequired(function Settings({}: Props) { + const queryClient = useQueryClient() + const colorMode = useColorMode() + const setColorMode = useSetColorMode() + const pal = usePalette('default') + const {_} = useLingui() + const setMinimalShellMode = useSetMinimalShellMode() + const requireAltTextEnabled = useRequireAltTextEnabled() + const setRequireAltTextEnabled = useSetRequireAltTextEnabled() + const onboardingDispatch = useOnboardingDispatch() + const navigation = useNavigation() + const {isMobile} = useWebMediaQueries() + const {screen, track} = useAnalytics() + const {openModal} = useModalControls() + const {isSwitchingAccounts, accounts, currentAccount, agent} = useSession() + const {clearCurrentAccount} = useSessionApi() + const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting(agent) + const {mutate: clearPreferences} = useClearPreferencesMutation() + const {data: invites} = useInviteCodesQuery() + const invitesAvailable = invites?.available?.length ?? 0 + + const primaryBg = useCustomPalette({ + light: {backgroundColor: colors.blue0}, + dark: {backgroundColor: colors.blue6}, + }) + const primaryText = useCustomPalette({ + light: {color: colors.blue3}, + dark: {color: colors.blue2}, + }) + + const dangerBg = useCustomPalette({ + light: {backgroundColor: colors.red1}, + dark: {backgroundColor: colors.red7}, + }) + const dangerText = useCustomPalette({ + light: {color: colors.red4}, + dark: {color: colors.red2}, + }) + + useFocusEffect( + React.useCallback(() => { + screen('Settings') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) + + const onPressAddAccount = React.useCallback(() => { + track('Settings:AddAccountButtonClicked') + navigation.navigate('HomeTab') + navigation.dispatch(StackActions.popToTop()) + clearCurrentAccount() + }, [track, navigation, clearCurrentAccount]) + + const onPressChangeHandle = React.useCallback(() => { + track('Settings:ChangeHandleButtonClicked') + openModal({ + name: 'change-handle', + onChanged() { + if (currentAccount) { + // refresh my profile + queryClient.invalidateQueries({ + queryKey: RQKEY_PROFILE(currentAccount.did), + }) + } + }, + }) + }, [track, queryClient, openModal, currentAccount]) + + const onPressInviteCodes = React.useCallback(() => { + track('Settings:InvitecodesButtonClicked') + openModal({name: 'invite-codes'}) + }, [track, openModal]) + + const onPressLanguageSettings = React.useCallback(() => { + navigation.navigate('LanguageSettings') + }, [navigation]) + + const onPressDeleteAccount = React.useCallback(() => { + openModal({name: 'delete-account'}) + }, [openModal]) + + const onPressResetPreferences = React.useCallback(async () => { + clearPreferences() + }, [clearPreferences]) + + const onPressResetOnboarding = React.useCallback(async () => { + onboardingDispatch({type: 'start'}) + Toast.show('Onboarding reset') + }, [onboardingDispatch]) + + const onPressBuildInfo = React.useCallback(() => { + Clipboard.setString( + `Build version: ${AppInfo.appVersion}; Platform: ${Platform.OS}`, ) - const {openModal} = useModalControls() - const {isSwitchingAccounts, accounts, currentAccount} = useSession() - const {clearCurrentAccount} = useSessionApi() - const {mutate: clearPreferences} = useClearPreferencesMutation() - const {data: invites} = useInviteCodesQuery() - const invitesAvailable = invites?.available?.length ?? 0 + Toast.show('Copied build version to clipboard') + }, []) - const primaryBg = useCustomPalette({ - light: {backgroundColor: colors.blue0}, - dark: {backgroundColor: colors.blue6}, - }) - const primaryText = useCustomPalette({ - light: {color: colors.blue3}, - dark: {color: colors.blue2}, - }) + const openHomeFeedPreferences = React.useCallback(() => { + navigation.navigate('PreferencesHomeFeed') + }, [navigation]) - const dangerBg = useCustomPalette({ - light: {backgroundColor: colors.red1}, - dark: {backgroundColor: colors.red7}, - }) - const dangerText = useCustomPalette({ - light: {color: colors.red4}, - dark: {color: colors.red2}, - }) + const openThreadsPreferences = React.useCallback(() => { + navigation.navigate('PreferencesThreads') + }, [navigation]) - useFocusEffect( - React.useCallback(() => { - screen('Settings') - setMinimalShellMode(false) - }, [screen, setMinimalShellMode]), - ) + const onPressAppPasswords = React.useCallback(() => { + navigation.navigate('AppPasswords') + }, [navigation]) - const onPressAddAccount = React.useCallback(() => { - track('Settings:AddAccountButtonClicked') - navigation.navigate('HomeTab') - navigation.dispatch(StackActions.popToTop()) - clearCurrentAccount() - }, [track, navigation, clearCurrentAccount]) + const onPressSystemLog = React.useCallback(() => { + navigation.navigate('Log') + }, [navigation]) - const onPressChangeHandle = React.useCallback(() => { - track('Settings:ChangeHandleButtonClicked') - openModal({ - name: 'change-handle', - onChanged() { - store.session.reloadFromServer().then( - () => { - Toast.show('Your handle has been updated') - }, - err => { - logger.error('Failed to reload from server after handle update', { - error: err, - }) - }, - ) - }, - }) - }, [track, store, openModal]) + const onPressStorybook = React.useCallback(() => { + navigation.navigate('Debug') + }, [navigation]) - const onPressInviteCodes = React.useCallback(() => { - track('Settings:InvitecodesButtonClicked') - openModal({name: 'invite-codes'}) - }, [track, openModal]) + const onPressSavedFeeds = React.useCallback(() => { + navigation.navigate('SavedFeeds') + }, [navigation]) - const onPressLanguageSettings = React.useCallback(() => { - navigation.navigate('LanguageSettings') - }, [navigation]) + const onPressStatusPage = React.useCallback(() => { + Linking.openURL(STATUS_PAGE_URL) + }, []) - const onPressDeleteAccount = React.useCallback(() => { - openModal({name: 'delete-account'}) - }, [openModal]) - - const onPressResetPreferences = React.useCallback(async () => { - clearPreferences() - }, [clearPreferences]) - - const onPressResetOnboarding = React.useCallback(async () => { - onboardingDispatch({type: 'start'}) - Toast.show('Onboarding reset') - }, [onboardingDispatch]) - - const onPressBuildInfo = React.useCallback(() => { - Clipboard.setString( - `Build version: ${AppInfo.appVersion}; Platform: ${Platform.OS}`, - ) - Toast.show('Copied build version to clipboard') - }, []) - - const openHomeFeedPreferences = React.useCallback(() => { - navigation.navigate('PreferencesHomeFeed') - }, [navigation]) - - const openThreadsPreferences = React.useCallback(() => { - navigation.navigate('PreferencesThreads') - }, [navigation]) - - const onPressAppPasswords = React.useCallback(() => { - navigation.navigate('AppPasswords') - }, [navigation]) - - const onPressSystemLog = React.useCallback(() => { - navigation.navigate('Log') - }, [navigation]) - - const onPressStorybook = React.useCallback(() => { - navigation.navigate('Debug') - }, [navigation]) - - const onPressSavedFeeds = React.useCallback(() => { - navigation.navigate('SavedFeeds') - }, [navigation]) - - const onPressStatusPage = React.useCallback(() => { - Linking.openURL(STATUS_PAGE_URL) - }, []) - - return ( - - - - - {currentAccount ? ( - <> - - Account + return ( + + + + + {currentAccount ? ( + <> + + Account + + + + Email:{' '} - - - Email:{' '} - - {currentAccount.emailConfirmed && ( - <> - - - )} - - {currentAccount.email}{' '} - - openModal({name: 'change-email'})}> - - Change - - - - - - Birthday:{' '} - - openModal({name: 'birth-date-settings'})}> - - Show - - - - - - {!currentAccount.emailConfirmed && } - - ) : null} - - - Signed in as - - - - - {isSwitchingAccounts ? ( - - - - ) : ( - - )} - - {accounts - .filter(a => a.did !== currentAccount?.did) - .map(account => ( - - ))} - - - - - - - Add account - - - - - - - Invite a Friend - - - - 0 ? primaryBg : pal.btn, - ]}> - 0 - ? primaryText - : pal.text) as FontAwesomeIconStyle - } - /> - - 0 ? pal.link : pal.text}> - {formatCount(invitesAvailable)} invite{' '} - {pluralize(invitesAvailable, 'code')} available - - - - - - - Accessibility - - - setRequireAltTextEnabled(!requireAltTextEnabled)} - /> - - - - - - Appearance - - - - setColorMode('system')} - accessibilityHint="Set color theme to system setting" - /> - setColorMode('light')} - accessibilityHint="Set color theme to light" - /> - setColorMode('dark')} - accessibilityHint="Set color theme to dark" - /> - - - - - - Basics - - - - - - - Home Feed Preferences - - - - - - - - Thread Preferences - - - - - - - - My Saved Feeds - - - - - - - - Languages - - - navigation.navigate('Moderation') - } - accessibilityRole="button" - accessibilityHint="" - accessibilityLabel={_(msg`Opens moderation settings`)}> - - - - - Moderation - - - - - - Advanced - - - - - - - App passwords - - - - - - - - Change handle - - - - - Danger Zone - - - - - - - Delete my account… - - - - - Developer Tools - - - - System log - - - {__DEV__ ? ( - - ) : null} - {__DEV__ ? ( - <> - - - Storybook - - - - - Reset preferences state - - - - - Reset onboarding state - - - - ) : null} - - - - - Build version {AppInfo.appVersion} {AppInfo.updateChannel} - + {currentAccount.emailConfirmed && ( + <> + + + )} + + {currentAccount.email}{' '} - - - ·   - - - - Status page + openModal({name: 'change-email'})}> + + Change + + + + + + Birthday:{' '} - - - - - - ) - }), -) + openModal({name: 'birth-date-settings'})}> + + Show + + + + -const EmailConfirmationNotice = observer( - function EmailConfirmationNoticeImpl() { - const pal = usePalette('default') - const palInverted = usePalette('inverted') - const {_} = useLingui() - const {isMobile} = useWebMediaQueries() - const {openModal} = useModalControls() - - return ( - - - Verify email - - - - openModal({name: 'verify-email'})}> - - - Verify My Email - - - - - Protect your account by verifying your email. + {!currentAccount.emailConfirmed && } + + ) : null} + + + Signed in as + + + {isSwitchingAccounts ? ( + + + + ) : ( + + )} + + {accounts + .filter(a => a.did !== currentAccount?.did) + .map(account => ( + + ))} + + + + + + + Add account + + + + + + + Invite a Friend + + + + 0 ? primaryBg : pal.btn, + ]}> + 0 + ? primaryText + : pal.text) as FontAwesomeIconStyle + } + /> + + 0 ? pal.link : pal.text}> + {formatCount(invitesAvailable)} invite{' '} + {pluralize(invitesAvailable, 'code')} available + + + + + + + Accessibility + + + setRequireAltTextEnabled(!requireAltTextEnabled)} + /> + + + + + + Appearance + + + + setColorMode('system')} + accessibilityHint="Set color theme to system setting" + /> + setColorMode('light')} + accessibilityHint="Set color theme to light" + /> + setColorMode('dark')} + accessibilityHint="Set color theme to dark" + /> + + + + + + Basics + + + + + + + Home Feed Preferences + + + + + + + + Thread Preferences + + + + + + + + My Saved Feeds + + + + + + + + Languages + + + navigation.navigate('Moderation') + } + accessibilityRole="button" + accessibilityHint="" + accessibilityLabel={_(msg`Opens moderation settings`)}> + + + + + Moderation + + + + + + Advanced + + + + + + + App passwords + + + + + + + + Change handle + + + + + Danger Zone + + + + + + + Delete my account… + + + + + Developer Tools + + + + System log + + + {__DEV__ ? ( + + ) : null} + {__DEV__ ? ( + <> + + + Storybook + + + + + Reset preferences state + + + + + Reset onboarding state + + + + ) : null} + + + + + Build version {AppInfo.appVersion} {AppInfo.updateChannel} + + + + + ·   + + + + Status page + + + + + + + ) +}) + +function EmailConfirmationNotice() { + const pal = usePalette('default') + const palInverted = usePalette('inverted') + const {_} = useLingui() + const {isMobile} = useWebMediaQueries() + const {openModal} = useModalControls() + + return ( + + + Verify email + + + + openModal({name: 'verify-email'})}> + + + Verify My Email + + + + + Protect your account by verifying your email. + - ) - }, -) + + ) +} const styles = StyleSheet.create({ dimmed: { diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 1ee359be..a67a105b 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -10,7 +10,6 @@ import { ViewStyle, } from 'react-native' import {useNavigation, StackActions} from '@react-navigation/native' -import {observer} from 'mobx-react-lite' import { FontAwesomeIcon, FontAwesomeIconStyle, @@ -101,7 +100,7 @@ export function DrawerProfileCard({ ) } -export const DrawerContent = observer(function DrawerContentImpl() { +export function DrawerContent() { const theme = useTheme() const pal = usePalette('default') const {_} = useLingui() @@ -404,7 +403,7 @@ export const DrawerContent = observer(function DrawerContentImpl() { ) -}) +} interface MenuItemProps extends ComponentProps { icon: JSX.Element @@ -458,11 +457,7 @@ function MenuItem({ ) } -const InviteCodes = observer(function InviteCodesImpl({ - style, -}: { - style?: StyleProp -}) { +function InviteCodes({style}: {style?: StyleProp}) { const {track} = useAnalytics() const setDrawerOpen = useSetDrawerOpen() const pal = usePalette('default') @@ -502,7 +497,7 @@ const InviteCodes = observer(function InviteCodesImpl({ ) -}) +} const styles = StyleSheet.create({ view: { diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx index a1502d2e..ed800679 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -4,7 +4,6 @@ import Animated from 'react-native-reanimated' import {StackActions} from '@react-navigation/native' import {BottomTabBarProps} from '@react-navigation/bottom-tabs' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {observer} from 'mobx-react-lite' import {Text} from 'view/com/util/text/Text' import {useAnalytics} from 'lib/analytics/analytics' import {clamp} from 'lib/numbers' @@ -34,9 +33,7 @@ import {useProfileQuery} from '#/state/queries/profile' type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds' -export const BottomBar = observer(function BottomBarImpl({ - navigation, -}: BottomTabBarProps) { +export function BottomBar({navigation}: BottomTabBarProps) { const {openModal} = useModalControls() const {currentAccount} = useSession() const pal = usePalette('default') @@ -231,7 +228,7 @@ export const BottomBar = observer(function BottomBarImpl({ /> ) -}) +} interface BtnProps extends Pick< diff --git a/src/view/shell/bottom-bar/BottomBarWeb.tsx b/src/view/shell/bottom-bar/BottomBarWeb.tsx index d5858050..32fa1943 100644 --- a/src/view/shell/bottom-bar/BottomBarWeb.tsx +++ b/src/view/shell/bottom-bar/BottomBarWeb.tsx @@ -1,5 +1,4 @@ import React from 'react' -import {observer} from 'mobx-react-lite' import {usePalette} from 'lib/hooks/usePalette' import {useNavigationState} from '@react-navigation/native' import Animated from 'react-native-reanimated' @@ -24,7 +23,7 @@ import {makeProfileLink} from 'lib/routes/links' import {CommonNavigatorParams} from 'lib/routes/types' import {useSession} from '#/state/session' -export const BottomBarWeb = observer(function BottomBarWebImpl() { +export function BottomBarWeb() { const {currentAccount} = useSession() const pal = usePalette('default') const safeAreaInsets = useSafeAreaInsets() @@ -111,7 +110,7 @@ export const BottomBarWeb = observer(function BottomBarWebImpl() { ) -}) +} const NavItem: React.FC<{ children: (props: {isActive: boolean}) => React.ReactChild diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index 9cb10517..dc5e311f 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -1,13 +1,12 @@ import React from 'react' import {View, StyleSheet} from 'react-native' import {useNavigationState} from '@react-navigation/native' -import {observer} from 'mobx-react-lite' import {usePalette} from 'lib/hooks/usePalette' import {TextLink} from 'view/com/util/Link' import {getCurrentRoute} from 'lib/routes/helpers' import {usePinnedFeedsInfos} from '#/state/queries/feed' -export const DesktopFeeds = observer(function DesktopFeeds() { +export function DesktopFeeds() { const pal = usePalette('default') const feeds = usePinnedFeedsInfos() @@ -54,7 +53,7 @@ export const DesktopFeeds = observer(function DesktopFeeds() { ) -}) +} function FeedItem({ title, diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 3ec68872..841a49d3 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,5 +1,4 @@ import React from 'react' -import {observer} from 'mobx-react-lite' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {PressableWithHover} from 'view/com/util/PressableWithHover' import { @@ -47,7 +46,7 @@ import {useComposerControls} from '#/state/shell/composer' import {useFetchHandle} from '#/state/queries/handle' import {emitSoftReset} from '#/state/events' -const ProfileCard = observer(function ProfileCardImpl() { +function ProfileCard() { const {currentAccount} = useSession() const {isLoading, data: profile} = useProfileQuery({did: currentAccount!.did}) const {isDesktop} = useWebMediaQueries() @@ -73,7 +72,7 @@ const ProfileCard = observer(function ProfileCardImpl() { /> ) -}) +} function BackBtn() { const {isTablet} = useWebMediaQueries() @@ -117,13 +116,7 @@ interface NavItemProps { iconFilled: JSX.Element label: string } -const NavItem = observer(function NavItemImpl({ - count, - href, - icon, - iconFilled, - label, -}: NavItemProps) { +function NavItem({count, href, icon, iconFilled, label}: NavItemProps) { const pal = usePalette('default') const {currentAccount} = useSession() const {isDesktop, isTablet} = useWebMediaQueries() @@ -192,7 +185,7 @@ const NavItem = observer(function NavItemImpl({ )} ) -}) +} function ComposeBtn() { const {currentAccount} = useSession() @@ -264,7 +257,7 @@ function ComposeBtn() { ) } -export const DesktopLeftNav = observer(function DesktopLeftNav() { +export function DesktopLeftNav() { const {currentAccount} = useSession() const pal = usePalette('default') const {isDesktop, isTablet} = useWebMediaQueries() @@ -422,7 +415,7 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() { ) -}) +} const styles = StyleSheet.create({ leftNav: { diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 9e17cdcd..3b94c12e 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -1,5 +1,4 @@ import React from 'react' -import {observer} from 'mobx-react-lite' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {usePalette} from 'lib/hooks/usePalette' @@ -16,7 +15,7 @@ import {useModalControls} from '#/state/modals' import {useSession} from '#/state/session' import {useInviteCodesQuery} from '#/state/queries/invites' -export const DesktopRightNav = observer(function DesktopRightNavImpl() { +export function DesktopRightNav() { const pal = usePalette('default') const palError = usePalette('error') const {isSandbox, hasSession, currentAccount} = useSession() @@ -80,9 +79,9 @@ export const DesktopRightNav = observer(function DesktopRightNavImpl() { ) -}) +} -const InviteCodes = observer(function InviteCodesImpl() { +function InviteCodes() { const pal = usePalette('default') const {openModal} = useModalControls() const {data: invites} = useInviteCodesQuery() @@ -118,7 +117,7 @@ const InviteCodes = observer(function InviteCodesImpl() { ) -}) +} const styles = StyleSheet.create({ rightNav: { diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index c0eed078..09f8ba98 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -1,5 +1,4 @@ import React, {useEffect} from 'react' -import {observer} from 'mobx-react-lite' import {View, StyleSheet, TouchableOpacity} from 'react-native' import {DesktopLeftNav} from './desktop/LeftNav' import {DesktopRightNav} from './desktop/RightNav' @@ -76,7 +75,7 @@ function ShellInner() { ) } -export const Shell: React.FC = observer(function ShellImpl() { +export const Shell: React.FC = function ShellImpl() { const pageBg = useColorSchemeStyle(styles.bgLight, styles.bgDark) return ( @@ -85,7 +84,7 @@ export const Shell: React.FC = observer(function ShellImpl() { ) -}) +} const styles = StyleSheet.create({ bgLight: {