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
zio/stable
Paul Frazee 2023-11-16 12:53:43 -08:00 committed by GitHub
parent e637798e05
commit 54faa7e176
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1084 additions and 1941 deletions

View File

@ -5,7 +5,6 @@ import React, {useState, useEffect} from 'react'
import {RootSiblingParent} from 'react-native-root-siblings' import {RootSiblingParent} from 'react-native-root-siblings'
import * as SplashScreen from 'expo-splash-screen' import * as SplashScreen from 'expo-splash-screen'
import {GestureHandlerRootView} from 'react-native-gesture-handler' import {GestureHandlerRootView} from 'react-native-gesture-handler'
import {observer} from 'mobx-react-lite'
import {QueryClientProvider} from '@tanstack/react-query' import {QueryClientProvider} from '@tanstack/react-query'
import 'view/icons' import 'view/icons'
@ -16,7 +15,6 @@ import {listenSessionDropped} from './state/events'
import {useColorMode} from 'state/shell' import {useColorMode} from 'state/shell'
import {ThemeProvider} from 'lib/ThemeContext' import {ThemeProvider} from 'lib/ThemeContext'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {Shell} from 'view/shell' import {Shell} from 'view/shell'
import * as notifications from 'lib/notifications/notifications' import * as notifications from 'lib/notifications/notifications'
import * as analytics from 'lib/analytics/analytics' import * as analytics from 'lib/analytics/analytics'
@ -44,21 +42,12 @@ i18n.activate('en')
SplashScreen.preventAutoHideAsync() SplashScreen.preventAutoHideAsync()
const InnerApp = observer(function AppImpl() { function InnerApp() {
const colorMode = useColorMode() const colorMode = useColorMode()
const {isInitialLoad} = useSession() const {isInitialLoad} = useSession()
const {resumeSession} = useSessionApi() const {resumeSession} = useSessionApi()
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
undefined,
)
// init // init
useEffect(() => {
setupState().then(store => {
setRootStore(store)
})
}, [])
useEffect(() => { useEffect(() => {
initReminders() initReminders()
analytics.init() analytics.init()
@ -72,7 +61,7 @@ const InnerApp = observer(function AppImpl() {
}, [resumeSession]) }, [resumeSession])
// show nothing prior to init // show nothing prior to init
if (!rootStore || isInitialLoad) { if (isInitialLoad) {
// TODO add a loading state // TODO add a loading state
return null return null
} }
@ -85,22 +74,20 @@ const InnerApp = observer(function AppImpl() {
<UnreadNotifsProvider> <UnreadNotifsProvider>
<ThemeProvider theme={colorMode}> <ThemeProvider theme={colorMode}>
<analytics.Provider> <analytics.Provider>
<RootStoreProvider value={rootStore}> <I18nProvider i18n={i18n}>
<I18nProvider i18n={i18n}> {/* All components should be within this provider */}
{/* All components should be within this provider */} <RootSiblingParent>
<RootSiblingParent> <GestureHandlerRootView style={s.h100pct}>
<GestureHandlerRootView style={s.h100pct}> <TestCtrls />
<TestCtrls /> <Shell />
<Shell /> </GestureHandlerRootView>
</GestureHandlerRootView> </RootSiblingParent>
</RootSiblingParent> </I18nProvider>
</I18nProvider>
</RootStoreProvider>
</analytics.Provider> </analytics.Provider>
</ThemeProvider> </ThemeProvider>
</UnreadNotifsProvider> </UnreadNotifsProvider>
) )
}) }
function App() { function App() {
const [isReady, setReady] = useState(false) const [isReady, setReady] = useState(false)

View File

@ -1,7 +1,6 @@
import 'lib/sentry' // must be near top import 'lib/sentry' // must be near top
import React, {useState, useEffect} from 'react' import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite'
import {QueryClientProvider} from '@tanstack/react-query' import {QueryClientProvider} from '@tanstack/react-query'
import {SafeAreaProvider} from 'react-native-safe-area-context' import {SafeAreaProvider} from 'react-native-safe-area-context'
import {RootSiblingParent} from 'react-native-root-siblings' 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 {init as initReminders} from '#/state/shell/reminders'
import {useColorMode} from 'state/shell' import {useColorMode} from 'state/shell'
import * as analytics from 'lib/analytics/analytics' import * as analytics from 'lib/analytics/analytics'
import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {Shell} from 'view/shell/index' import {Shell} from 'view/shell/index'
import {ToastContainer} from 'view/com/util/Toast.web' import {ToastContainer} from 'view/com/util/Toast.web'
import {ThemeProvider} from 'lib/ThemeContext' import {ThemeProvider} from 'lib/ThemeContext'
@ -34,21 +32,12 @@ import {
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
const InnerApp = observer(function AppImpl() { function InnerApp() {
const {isInitialLoad} = useSession() const {isInitialLoad} = useSession()
const {resumeSession} = useSessionApi() const {resumeSession} = useSessionApi()
const colorMode = useColorMode() const colorMode = useColorMode()
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
undefined,
)
// init // init
useEffect(() => {
setupState().then(store => {
setRootStore(store)
})
}, [])
useEffect(() => { useEffect(() => {
initReminders() initReminders()
analytics.init() analytics.init()
@ -59,7 +48,7 @@ const InnerApp = observer(function AppImpl() {
}, [resumeSession]) }, [resumeSession])
// show nothing prior to init // show nothing prior to init
if (!rootStore || isInitialLoad) { if (isInitialLoad) {
// TODO add a loading state // TODO add a loading state
return null return null
} }
@ -72,22 +61,20 @@ const InnerApp = observer(function AppImpl() {
<UnreadNotifsProvider> <UnreadNotifsProvider>
<ThemeProvider theme={colorMode}> <ThemeProvider theme={colorMode}>
<analytics.Provider> <analytics.Provider>
<RootStoreProvider value={rootStore}> <I18nProvider i18n={i18n}>
<I18nProvider i18n={i18n}> {/* All components should be within this provider */}
{/* All components should be within this provider */} <RootSiblingParent>
<RootSiblingParent> <SafeAreaProvider>
<SafeAreaProvider> <Shell />
<Shell /> </SafeAreaProvider>
</SafeAreaProvider> </RootSiblingParent>
</RootSiblingParent> </I18nProvider>
</I18nProvider> <ToastContainer />
<ToastContainer />
</RootStoreProvider>
</analytics.Provider> </analytics.Provider>
</ThemeProvider> </ThemeProvider>
</UnreadNotifsProvider> </UnreadNotifsProvider>
) )
}) }
function App() { function App() {
const [isReady, setReady] = useState(false) const [isReady, setReady] = useState(false)

View File

@ -7,13 +7,21 @@ import {
useAnalytics as useAnalyticsOrig, useAnalytics as useAnalyticsOrig,
ClientMethods, ClientMethods,
} from '@segment/analytics-react-native' } from '@segment/analytics-react-native'
import {AppInfo} from 'state/models/root-store' import {z} from 'zod'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {sha256} from 'js-sha256' import {sha256} from 'js-sha256'
import {ScreenEvent, TrackEvent} from './types' import {ScreenEvent, TrackEvent} from './types'
import {logger} from '#/logger' import {logger} from '#/logger'
import {listenSessionLoaded} from '#/state/events' 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<typeof appInfo>
const segmentClient = createClient({ const segmentClient = createClient({
writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI', writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI',
trackAppLifecycleEvents: false, trackAppLifecycleEvents: false,
@ -128,7 +136,11 @@ async function writeAppInfo(value: AppInfo) {
await AsyncStorage.setItem('BSKY_APP_INFO', JSON.stringify(value)) await AsyncStorage.setItem('BSKY_APP_INFO', JSON.stringify(value))
} }
async function readAppInfo(): Promise<Partial<AppInfo> | undefined> { async function readAppInfo(): Promise<AppInfo | undefined> {
const rawData = await AsyncStorage.getItem('BSKY_APP_INFO') 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
} }

View File

@ -10,7 +10,6 @@ import {
RichText, RichText,
} from '@atproto/api' } from '@atproto/api'
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import {RootStoreModel} from 'state/models/root-store'
import {isNetworkError} from 'lib/strings/errors' import {isNetworkError} from 'lib/strings/errors'
import {LinkMeta} from '../link-meta/link-meta' import {LinkMeta} from '../link-meta/link-meta'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
@ -26,33 +25,6 @@ export interface ExternalEmbedDraft {
localThumb?: ImageModel 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( export async function uploadBlob(
agent: BskyAgent, agent: BskyAgent,
blob: string, blob: string,

View File

@ -1,68 +0,0 @@
import {runInAction} from 'mobx'
import {deepObserve} from 'mobx-utils'
import set from 'lodash.set'
const ongoingActions = new Set<any>()
/**
* 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<string, any>,
U,
>(
model: T,
preUpdate: () => void,
serverUpdate: () => Promise<U>,
postUpdate?: (res: U) => void,
): Promise<void> => {
if (ongoingActions.has(model)) {
return
}
ongoingActions.add(model)
const prevState: Map<string, any> = new Map<string, any>()
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)
}
}

View File

@ -1,9 +1,8 @@
import {AppBskyFeedPost} from '@atproto/api' import {AppBskyFeedPost, BskyAgent} from '@atproto/api'
import * as apilib from 'lib/api/index' import * as apilib from 'lib/api/index'
import {LikelyType, LinkMeta} from './link-meta' import {LikelyType, LinkMeta} from './link-meta'
// import {match as matchRoute} from 'view/routes' // import {match as matchRoute} from 'view/routes'
import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers' import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers'
import {RootStoreModel} from 'state/index'
import {ComposerOptsQuote} from 'state/shell/composer' import {ComposerOptsQuote} from 'state/shell/composer'
import {useGetPost} from '#/state/queries/post' import {useGetPost} from '#/state/queries/post'
@ -23,7 +22,7 @@ import {useGetPost} from '#/state/queries/post'
// remove once that's implemented // remove once that's implemented
// -prf // -prf
export async function extractBskyMeta( export async function extractBskyMeta(
store: RootStoreModel, agent: BskyAgent,
url: string, url: string,
): Promise<LinkMeta> { ): Promise<LinkMeta> {
url = convertBskyAppUrlIfNeeded(url) url = convertBskyAppUrlIfNeeded(url)
@ -120,13 +119,13 @@ export async function getPostAsQuote(
} }
export async function getFeedAsEmbed( export async function getFeedAsEmbed(
store: RootStoreModel, agent: BskyAgent,
url: string, url: string,
): Promise<apilib.ExternalEmbedDraft> { ): Promise<apilib.ExternalEmbedDraft> {
url = convertBskyAppUrlIfNeeded(url) url = convertBskyAppUrlIfNeeded(url)
const [_0, user, _1, rkey] = url.split('/').filter(Boolean) const [_0, user, _1, rkey] = url.split('/').filter(Boolean)
const feed = makeRecordUri(user, 'app.bsky.feed.generator', rkey) 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 { return {
isLoading: false, isLoading: false,
uri: feed, uri: feed,
@ -146,13 +145,13 @@ export async function getFeedAsEmbed(
} }
export async function getListAsEmbed( export async function getListAsEmbed(
store: RootStoreModel, agent: BskyAgent,
url: string, url: string,
): Promise<apilib.ExternalEmbedDraft> { ): Promise<apilib.ExternalEmbedDraft> {
url = convertBskyAppUrlIfNeeded(url) url = convertBskyAppUrlIfNeeded(url)
const [_0, user, _1, rkey] = url.split('/').filter(Boolean) const [_0, user, _1, rkey] = url.split('/').filter(Boolean)
const list = makeRecordUri(user, 'app.bsky.graph.list', rkey) 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 { return {
isLoading: false, isLoading: false,
uri: list, uri: list,

View File

@ -1,5 +1,5 @@
import {BskyAgent} from '@atproto/api'
import {isBskyAppUrl} from '../strings/url-helpers' import {isBskyAppUrl} from '../strings/url-helpers'
import {RootStoreModel} from 'state/index'
import {extractBskyMeta} from './bsky' import {extractBskyMeta} from './bsky'
import {LINK_META_PROXY} from 'lib/constants' import {LINK_META_PROXY} from 'lib/constants'
@ -23,12 +23,12 @@ export interface LinkMeta {
} }
export async function getLinkMeta( export async function getLinkMeta(
store: RootStoreModel, agent: BskyAgent,
url: string, url: string,
timeout = 5e3, timeout = 5e3,
): Promise<LinkMeta> { ): Promise<LinkMeta> {
if (isBskyAppUrl(url)) { if (isBskyAppUrl(url)) {
return extractBskyMeta(store, url) return extractBskyMeta(agent, url)
} }
let urlp let urlp
@ -55,9 +55,9 @@ export async function getLinkMeta(
const to = setTimeout(() => controller.abort(), timeout || 5e3) const to = setTimeout(() => controller.abort(), timeout || 5e3)
const response = await fetch( const response = await fetch(
`${LINK_META_PROXY( `${LINK_META_PROXY(agent.service.toString() || '')}${encodeURIComponent(
store.session.currentSession?.service || '', url,
)}${encodeURIComponent(url)}`, )}`,
{signal: controller.signal}, {signal: controller.signal},
) )

View File

@ -0,0 +1,34 @@
import {Image} from 'react-native'
import type {Dimensions} from 'lib/media/types'
const sizes: Map<string, Dimensions> = new Map()
const activeRequests: Map<string, Promise<Dimensions>> = new Map()
export function get(uri: string): Dimensions | undefined {
return sizes.get(uri)
}
export async function fetch(uri: string): Promise<Dimensions> {
const Dimensions = sizes.get(uri)
if (Dimensions) {
return Dimensions
}
const prom =
activeRequests.get(uri) ||
new Promise<Dimensions>(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
}

View File

@ -1,5 +1,5 @@
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import {PROD_SERVICE} from 'state/index' import {PROD_SERVICE} from 'lib/constants'
import TLDs from 'tlds' import TLDs from 'tlds'
import psl from 'psl' import psl from 'psl'

View File

@ -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'

View File

@ -1,5 +0,0 @@
import {LRUMap} from 'lru_map'
export class HandleResolutionsCache {
cache: LRUMap<string, string> = new LRUMap(500)
}

View File

@ -1,38 +0,0 @@
import {Image} from 'react-native'
import type {Dimensions} from 'lib/media/types'
export class ImageSizesCache {
sizes: Map<string, Dimensions> = new Map()
activeRequests: Map<string, Promise<Dimensions>> = new Map()
constructor() {}
get(uri: string): Dimensions | undefined {
return this.sizes.get(uri)
}
async fetch(uri: string): Promise<Dimensions> {
const Dimensions = this.sizes.get(uri)
if (Dimensions) {
return Dimensions
}
const prom =
this.activeRequests.get(uri) ||
new Promise<Dimensions>(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
}
}

View File

@ -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> | LinkMeta
export class LinkMetasCache {
cache: LRUMap<string, CacheValue> = 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
}
}
}

View File

@ -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<string, PostView> = 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,
}
}

View File

@ -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> | GetProfile.Response
export class ProfilesCache {
cache: LRUMap<string, CacheValue> = 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)
}
}

View File

@ -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
}
})
}
}

View File

@ -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<typeof appInfo>
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<RootStoreModel>(throwawayInst)
export const RootStoreProvider = RootStoreContext.Provider
export const useStores = () => useContext(RootStoreContext)

View File

@ -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<ServiceDescription> {
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() {}
}

View File

@ -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()
}
})
}
}

View File

@ -1,6 +1,4 @@
import {SessionModel} from '../models/session' export function shouldRequestEmailConfirmation() {
export function shouldRequestEmailConfirmation(_session: SessionModel) {
return false return false
} }

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {SafeAreaView} from 'react-native' import {SafeAreaView} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Login} from 'view/com/auth/login/Login' import {Login} from 'view/com/auth/login/Login'
import {CreateAccount} from 'view/com/auth/create/CreateAccount' import {CreateAccount} from 'view/com/auth/create/CreateAccount'
import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
@ -16,7 +15,7 @@ enum ScreenState {
S_CreateAccount, S_CreateAccount,
} }
export const LoggedOut = observer(function LoggedOutImpl() { export function LoggedOut() {
const pal = usePalette('default') const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {screen} = useAnalytics() const {screen} = useAnalytics()
@ -58,4 +57,4 @@ export const LoggedOut = observer(function LoggedOutImpl() {
</ErrorBoundary> </ErrorBoundary>
</SafeAreaView> </SafeAreaView>
) )
}) }

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {SafeAreaView} from 'react-native' import {SafeAreaView} from 'react-native'
import {observer} from 'mobx-react-lite'
import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -10,7 +9,7 @@ import {RecommendedFollows} from './onboarding/RecommendedFollows'
import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' import {useSetMinimalShellMode} from '#/state/shell/minimal-mode'
import {useOnboardingState, useOnboardingDispatch} from '#/state/shell' import {useOnboardingState, useOnboardingDispatch} from '#/state/shell'
export const Onboarding = observer(function OnboardingImpl() { export function Onboarding() {
const pal = usePalette('default') const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const onboardingState = useOnboardingState() const onboardingState = useOnboardingState()
@ -38,4 +37,4 @@ export const Onboarding = observer(function OnboardingImpl() {
</ErrorBoundary> </ErrorBoundary>
</SafeAreaView> </SafeAreaView>
) )
}) }

View File

@ -4,12 +4,14 @@ import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import {ComAtprotoServerDescribeServer} from '@atproto/api'
import {TextLink} from '../../util/Link' import {TextLink} from '../../util/Link'
import {Text} from '../../util/text/Text' import {Text} from '../../util/text/Text'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {ServiceDescription} from 'state/models/session'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
export const Policies = ({ export const Policies = ({
serviceDescription, serviceDescription,
needsGuardian, needsGuardian,

View File

@ -13,7 +13,7 @@ import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' 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' import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
/** STEP 1: Your hosting provider /** STEP 1: Your hosting provider

View File

@ -9,13 +9,13 @@ import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import {ComAtprotoServerDescribeServer} from '@atproto/api'
import * as EmailValidator from 'email-validator' import * as EmailValidator from 'email-validator'
import {BskyAgent} from '@atproto/api' import {BskyAgent} from '@atproto/api'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {Text} from '../../util/text/Text' import {Text} from '../../util/text/Text'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {toNiceDomain} from 'lib/strings/url-helpers' import {toNiceDomain} from 'lib/strings/url-helpers'
import {ServiceDescription} from 'state/models/session'
import {isNetworkError} from 'lib/strings/errors' import {isNetworkError} from 'lib/strings/errors'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
@ -26,6 +26,8 @@ import {useLingui} from '@lingui/react'
import {styles} from './styles' import {styles} from './styles'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
export const ForgotPasswordForm = ({ export const ForgotPasswordForm = ({
error, error,
serviceUrl, serviceUrl,

View File

@ -2,8 +2,7 @@ import React, {useState, useEffect} from 'react'
import {KeyboardAvoidingView} from 'react-native' import {KeyboardAvoidingView} from 'react-native'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {LoggedOutLayout} from 'view/com/util/layouts/LoggedOutLayout' import {LoggedOutLayout} from 'view/com/util/layouts/LoggedOutLayout'
import {useStores, DEFAULT_SERVICE} from 'state/index' import {DEFAULT_SERVICE} from '#/lib/constants'
import {ServiceDescription} from 'state/models/session'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {logger} from '#/logger' import {logger} from '#/logger'
import {ChooseAccountForm} from './ChooseAccountForm' import {ChooseAccountForm} from './ChooseAccountForm'
@ -14,6 +13,7 @@ import {PasswordUpdatedForm} from './PasswordUpdatedForm'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useSession, SessionAccount} from '#/state/session' import {useSession, SessionAccount} from '#/state/session'
import {useServiceQuery} from '#/state/queries/service'
enum Forms { enum Forms {
Login, Login,
@ -25,20 +25,20 @@ enum Forms {
export const Login = ({onPressBack}: {onPressBack: () => void}) => { export const Login = ({onPressBack}: {onPressBack: () => void}) => {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores()
const {accounts} = useSession() const {accounts} = useSession()
const {track} = useAnalytics() const {track} = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const [retryDescribeTrigger, setRetryDescribeTrigger] = useState<any>({})
const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE) const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE)
const [serviceDescription, setServiceDescription] = useState<
ServiceDescription | undefined
>(undefined)
const [initialHandle, setInitialHandle] = useState<string>('') const [initialHandle, setInitialHandle] = useState<string>('')
const [currentForm, setCurrentForm] = useState<Forms>( const [currentForm, setCurrentForm] = useState<Forms>(
accounts.length ? Forms.ChooseAccount : Forms.Login, accounts.length ? Forms.ChooseAccount : Forms.Login,
) )
const {
data: serviceDescription,
error: serviceError,
refetch: refetchService,
} = useServiceQuery(serviceUrl)
const onSelectAccount = (account?: SessionAccount) => { const onSelectAccount = (account?: SessionAccount) => {
if (account?.service) { if (account?.service) {
@ -54,35 +54,21 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
} }
useEffect(() => { useEffect(() => {
let aborted = false if (serviceError) {
setError('') setError(
store.session.describeService(serviceUrl).then( _(
desc => { msg`Unable to contact your service. Please check your Internet connection.`,
if (aborted) { ),
return )
} logger.warn(`Failed to fetch service description for ${serviceUrl}`, {
setServiceDescription(desc) error: String(serviceError),
}, })
err => { } else {
if (aborted) { setError('')
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
} }
}, [store.session, serviceUrl, retryDescribeTrigger, _]) }, [serviceError, serviceUrl, _])
const onPressRetryConnect = () => setRetryDescribeTrigger({}) const onPressRetryConnect = () => refetchService()
const onPressForgotPassword = () => { const onPressForgotPassword = () => {
track('Signin:PressedForgotPassword') track('Signin:PressedForgotPassword')
setCurrentForm(Forms.ForgotPassword) setCurrentForm(Forms.ForgotPassword)

View File

@ -10,12 +10,12 @@ import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import {ComAtprotoServerDescribeServer} from '@atproto/api'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {Text} from '../../util/text/Text' import {Text} from '../../util/text/Text'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {createFullHandle} from 'lib/strings/handles' import {createFullHandle} from 'lib/strings/handles'
import {toNiceDomain} from 'lib/strings/url-helpers' import {toNiceDomain} from 'lib/strings/url-helpers'
import {ServiceDescription} from 'state/models/session'
import {isNetworkError} from 'lib/strings/errors' import {isNetworkError} from 'lib/strings/errors'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
@ -27,6 +27,8 @@ import {styles} from './styles'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
export const LoginForm = ({ export const LoginForm = ({
error, error,
serviceUrl, serviceUrl,

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints' import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
@ -16,9 +15,7 @@ import {useSuggestedFeedsQuery} from '#/state/queries/suggested-feeds'
type Props = { type Props = {
next: () => void next: () => void
} }
export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ export function RecommendedFeeds({next}: Props) {
next,
}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const {isTabletOrMobile} = useWebMediaQueries() const {isTabletOrMobile} = useWebMediaQueries()
const {isLoading, data} = useSuggestedFeedsQuery() const {isLoading, data} = useSuggestedFeedsQuery()
@ -146,7 +143,7 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({
</Mobile> </Mobile>
</> </>
) )
}) }
const tdStyles = StyleSheet.create({ const tdStyles = StyleSheet.create({
container: { container: {

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {AppBskyFeedDefs, RichText as BskRichText} from '@atproto/api' import {AppBskyFeedDefs, RichText as BskRichText} from '@atproto/api'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
@ -19,7 +18,7 @@ import {
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {logger} from '#/logger' import {logger} from '#/logger'
export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ export function RecommendedFeedsItem({
item, item,
}: { }: {
item: AppBskyFeedDefs.GeneratorView item: AppBskyFeedDefs.GeneratorView
@ -164,4 +163,4 @@ export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({
</View> </View>
</View> </View>
) )
}) }

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {AppBskyActorDefs, moderateProfile} from '@atproto/api' import {AppBskyActorDefs, moderateProfile} from '@atproto/api'
import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints' import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints'
@ -19,9 +18,7 @@ import {logger} from '#/logger'
type Props = { type Props = {
next: () => void next: () => void
} }
export const RecommendedFollows = observer(function RecommendedFollowsImpl({ export function RecommendedFollows({next}: Props) {
next,
}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const {isTabletOrMobile} = useWebMediaQueries() const {isTabletOrMobile} = useWebMediaQueries()
const {data: suggestedFollows, dataUpdatedAt} = useSuggestedFollowsQuery() const {data: suggestedFollows, dataUpdatedAt} = useSuggestedFollowsQuery()
@ -211,7 +208,7 @@ export const RecommendedFollows = observer(function RecommendedFollowsImpl({
</Mobile> </Mobile>
</> </>
) )
}) }
const tdStyles = StyleSheet.create({ const tdStyles = StyleSheet.create({
container: { container: {

View File

@ -7,16 +7,13 @@ import {usePalette} from 'lib/hooks/usePalette'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout' import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout'
import {Button} from 'view/com/util/forms/Button' import {Button} from 'view/com/util/forms/Button'
import {observer} from 'mobx-react-lite'
type Props = { type Props = {
next: () => void next: () => void
skip: () => void skip: () => void
} }
export const WelcomeDesktop = observer(function WelcomeDesktopImpl({ export function WelcomeDesktop({next}: Props) {
next,
}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const horizontal = useMediaQuery({minWidth: 1300}) const horizontal = useMediaQuery({minWidth: 1300})
const title = ( const title = (
@ -105,7 +102,7 @@ export const WelcomeDesktop = observer(function WelcomeDesktopImpl({
</View> </View>
</TitleColumnLayout> </TitleColumnLayout>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
row: { row: {

View File

@ -5,7 +5,6 @@ import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Button} from 'view/com/util/forms/Button' import {Button} from 'view/com/util/forms/Button'
import {observer} from 'mobx-react-lite'
import {ViewHeader} from 'view/com/util/ViewHeader' import {ViewHeader} from 'view/com/util/ViewHeader'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
@ -14,10 +13,7 @@ type Props = {
skip: () => void skip: () => void
} }
export const WelcomeMobile = observer(function WelcomeMobileImpl({ export function WelcomeMobile({next, skip}: Props) {
next,
skip,
}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
@ -102,7 +98,7 @@ export const WelcomeMobile = observer(function WelcomeMobileImpl({
/> />
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -5,7 +5,6 @@ import {
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
} from 'react-native' } from 'react-native'
import {observer} from 'mobx-react-lite'
import {CenteredView} from '../util/Views' import {CenteredView} from '../util/Views'
import {LoggedOut} from './LoggedOut' import {LoggedOut} from './LoggedOut'
import {Onboarding} from './Onboarding' import {Onboarding} from './Onboarding'
@ -18,7 +17,7 @@ import {useSession} from '#/state/session'
export const withAuthRequired = <P extends object>( export const withAuthRequired = <P extends object>(
Component: React.ComponentType<P>, Component: React.ComponentType<P>,
): React.FC<P> => ): React.FC<P> =>
observer(function AuthRequired(props: P) { function AuthRequired(props: P) {
const {isInitialLoad, hasSession} = useSession() const {isInitialLoad, hasSession} = useSession()
const onboardingState = useOnboardingState() const onboardingState = useOnboardingState()
if (isInitialLoad) { if (isInitialLoad) {
@ -31,7 +30,7 @@ export const withAuthRequired = <P extends object>(
return <Onboarding /> return <Onboarding />
} }
return <Component {...props} /> return <Component {...props} />
}) }
function Loading() { function Loading() {
const pal = usePalette('default') const pal = usePalette('default')

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {Keyboard, StyleSheet} from 'react-native' import {Keyboard, StyleSheet} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Button} from 'view/com/util/forms/Button' import {Button} from 'view/com/util/forms/Button'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {ShieldExclamation} from 'lib/icons' import {ShieldExclamation} from 'lib/icons'
@ -11,7 +10,7 @@ import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
export const LabelsBtn = observer(function LabelsBtn({ export function LabelsBtn({
labels, labels,
hasMedia, hasMedia,
onChange, onChange,
@ -49,7 +48,7 @@ export const LabelsBtn = observer(function LabelsBtn({
) : null} ) : null}
</Button> </Button>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
button: { button: {

View File

@ -1,6 +1,5 @@
import React, {useCallback, useMemo} from 'react' import React, {useCallback, useMemo} from 'react'
import {StyleSheet, Keyboard} from 'react-native' import {StyleSheet, Keyboard} from 'react-native'
import {observer} from 'mobx-react-lite'
import { import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, FontAwesomeIconStyle,
@ -24,7 +23,7 @@ import {
import {t, msg} from '@lingui/macro' import {t, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
export const SelectLangBtn = observer(function SelectLangBtn() { export function SelectLangBtn() {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {openModal} = useModalControls() const {openModal} = useModalControls()
@ -117,7 +116,7 @@ export const SelectLangBtn = observer(function SelectLangBtn() {
)} )}
</DropdownButton> </DropdownButton>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
button: { button: {

View File

@ -1,6 +1,5 @@
import React, {useEffect, useRef} from 'react' import React, {useEffect, useRef} from 'react'
import {Animated, TouchableOpacity, StyleSheet, View} from 'react-native' import {Animated, TouchableOpacity, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {Text} from 'view/com/util/text/Text' 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 {Trans} from '@lingui/macro'
import {AppBskyActorDefs} from '@atproto/api' import {AppBskyActorDefs} from '@atproto/api'
export const Autocomplete = observer(function AutocompleteImpl({ export function Autocomplete({
prefix, prefix,
onSelect, onSelect,
}: { }: {
@ -103,7 +102,7 @@ export const Autocomplete = observer(function AutocompleteImpl({
) : null} ) : null}
</Animated.View> </Animated.View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -1,5 +1,4 @@
import {useState, useEffect} from 'react' import {useState, useEffect} from 'react'
import {useStores} from 'state/index'
import {ImageModel} from 'state/models/media/image' import {ImageModel} from 'state/models/media/image'
import * as apilib from 'lib/api/index' import * as apilib from 'lib/api/index'
import {getLinkMeta} from 'lib/link-meta/link-meta' import {getLinkMeta} from 'lib/link-meta/link-meta'
@ -17,6 +16,7 @@ import {
import {ComposerOpts} from 'state/shell/composer' import {ComposerOpts} from 'state/shell/composer'
import {POST_IMG_MAX} from 'lib/constants' import {POST_IMG_MAX} from 'lib/constants'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useSession} from '#/state/session'
import {useGetPost} from '#/state/queries/post' import {useGetPost} from '#/state/queries/post'
export function useExternalLinkFetch({ export function useExternalLinkFetch({
@ -24,7 +24,7 @@ export function useExternalLinkFetch({
}: { }: {
setQuote: (opts: ComposerOpts['quote']) => void setQuote: (opts: ComposerOpts['quote']) => void
}) { }) {
const store = useStores() const {agent} = useSession()
const [extLink, setExtLink] = useState<apilib.ExternalEmbedDraft | undefined>( const [extLink, setExtLink] = useState<apilib.ExternalEmbedDraft | undefined>(
undefined, undefined,
) )
@ -56,7 +56,7 @@ export function useExternalLinkFetch({
}, },
) )
} else if (isBskyCustomFeedUrl(extLink.uri)) { } else if (isBskyCustomFeedUrl(extLink.uri)) {
getFeedAsEmbed(store, extLink.uri).then( getFeedAsEmbed(agent, extLink.uri).then(
({embed, meta}) => { ({embed, meta}) => {
if (aborted) { if (aborted) {
return return
@ -74,7 +74,7 @@ export function useExternalLinkFetch({
}, },
) )
} else if (isBskyListUrl(extLink.uri)) { } else if (isBskyListUrl(extLink.uri)) {
getListAsEmbed(store, extLink.uri).then( getListAsEmbed(agent, extLink.uri).then(
({embed, meta}) => { ({embed, meta}) => {
if (aborted) { if (aborted) {
return return
@ -92,7 +92,7 @@ export function useExternalLinkFetch({
}, },
) )
} else { } else {
getLinkMeta(store, extLink.uri).then(meta => { getLinkMeta(agent, extLink.uri).then(meta => {
if (aborted) { if (aborted) {
return return
} }
@ -134,7 +134,7 @@ export function useExternalLinkFetch({
}) })
} }
return cleanup return cleanup
}, [store, extLink, setQuote, getPost]) }, [agent, extLink, setQuote, getPost])
return {extLink, setExtLink} return {extLink, setExtLink}
} }

View File

@ -315,7 +315,6 @@ const ImageItem = ({
<GestureDetector gesture={composedGesture}> <GestureDetector gesture={composedGesture}>
<AnimatedImage <AnimatedImage
contentFit="contain" contentFit="contain"
// NOTE: Don't pass imageSrc={imageSrc} or MobX will break.
source={{uri: imageSrc.uri}} source={{uri: imageSrc.uri}}
style={[styles.image, animatedStyle]} style={[styles.image, animatedStyle]}
accessibilityLabel={imageSrc.alt} accessibilityLabel={imageSrc.alt}

View File

@ -139,7 +139,6 @@ const ImageItem = ({imageSrc, onTap, onZoom, onRequestClose}: Props) => {
{(!loaded || !imageDimensions) && <ImageLoading />} {(!loaded || !imageDimensions) && <ImageLoading />}
<AnimatedImage <AnimatedImage
contentFit="contain" contentFit="contain"
// NOTE: Don't pass imageSrc={imageSrc} or MobX will break.
source={{uri: imageSrc.uri}} source={{uri: imageSrc.uri}}
style={[styles.image, animatedStyle]} style={[styles.image, animatedStyle]}
accessibilityLabel={imageSrc.alt} accessibilityLabel={imageSrc.alt}

View File

@ -5,7 +5,6 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native' } from 'react-native'
import {observer} from 'mobx-react-lite'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {DateInput} from '../util/forms/DateInput' import {DateInput} from '../util/forms/DateInput'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
@ -103,7 +102,7 @@ function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) {
) )
} }
export const Component = observer(function Component({}: {}) { export function Component({}: {}) {
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
return !preferences ? ( return !preferences ? (
@ -111,7 +110,7 @@ export const Component = observer(function Component({}: {}) {
) : ( ) : (
<Inner preferences={preferences} /> <Inner preferences={preferences} />
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -1,7 +1,6 @@
import React, {useState} from 'react' import React, {useState} from 'react'
import {ActivityIndicator, SafeAreaView, StyleSheet, View} from 'react-native' import {ActivityIndicator, SafeAreaView, StyleSheet, View} from 'react-native'
import {ScrollView, TextInput} from './util' import {ScrollView, TextInput} from './util'
import {observer} from 'mobx-react-lite'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
@ -24,7 +23,7 @@ enum Stages {
export const snapPoints = ['90%'] export const snapPoints = ['90%']
export const Component = observer(function Component({}: {}) { export function Component() {
const pal = usePalette('default') const pal = usePalette('default')
const {agent, currentAccount} = useSession() const {agent, currentAccount} = useSession()
const {updateCurrentAccount} = useSessionApi() const {updateCurrentAccount} = useSessionApi()
@ -226,7 +225,7 @@ export const Component = observer(function Component({}: {}) {
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
titleSection: { titleSection: {

View File

@ -2,7 +2,6 @@ import React from 'react'
import {LabelPreference} from '@atproto/api' import {LabelPreference} from '@atproto/api'
import {StyleSheet, Pressable, View} from 'react-native' import {StyleSheet, Pressable, View} from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {observer} from 'mobx-react-lite'
import {ScrollView} from './util' import {ScrollView} from './util'
import {s, colors, gradients} from 'lib/styles' import {s, colors, gradients} from 'lib/styles'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
@ -28,82 +27,80 @@ import {
export const snapPoints = ['90%'] export const snapPoints = ['90%']
export const Component = observer( export function Component({}: {}) {
function ContentFilteringSettingsImpl({}: {}) { const {isMobile} = useWebMediaQueries()
const {isMobile} = useWebMediaQueries() const pal = usePalette('default')
const pal = usePalette('default') const {_} = useLingui()
const {_} = useLingui() const {closeModal} = useModalControls()
const {closeModal} = useModalControls() const {data: preferences} = usePreferencesQuery()
const {data: preferences} = usePreferencesQuery()
const onPressDone = React.useCallback(() => { const onPressDone = React.useCallback(() => {
closeModal() closeModal()
}, [closeModal]) }, [closeModal])
return ( return (
<View testID="contentFilteringModal" style={[pal.view, styles.container]}> <View testID="contentFilteringModal" style={[pal.view, styles.container]}>
<Text style={[pal.text, styles.title]}> <Text style={[pal.text, styles.title]}>
<Trans>Content Filtering</Trans> <Trans>Content Filtering</Trans>
</Text> </Text>
<ScrollView style={styles.scrollContainer}> <ScrollView style={styles.scrollContainer}>
<AdultContentEnabledPref /> <AdultContentEnabledPref />
<ContentLabelPref <ContentLabelPref
preferences={preferences} preferences={preferences}
labelGroup="nsfw" labelGroup="nsfw"
disabled={!preferences?.adultContentEnabled} disabled={!preferences?.adultContentEnabled}
/> />
<ContentLabelPref <ContentLabelPref
preferences={preferences} preferences={preferences}
labelGroup="nudity" labelGroup="nudity"
disabled={!preferences?.adultContentEnabled} disabled={!preferences?.adultContentEnabled}
/> />
<ContentLabelPref <ContentLabelPref
preferences={preferences} preferences={preferences}
labelGroup="suggestive" labelGroup="suggestive"
disabled={!preferences?.adultContentEnabled} disabled={!preferences?.adultContentEnabled}
/> />
<ContentLabelPref <ContentLabelPref
preferences={preferences} preferences={preferences}
labelGroup="gore" labelGroup="gore"
disabled={!preferences?.adultContentEnabled} disabled={!preferences?.adultContentEnabled}
/> />
<ContentLabelPref preferences={preferences} labelGroup="hate" /> <ContentLabelPref preferences={preferences} labelGroup="hate" />
<ContentLabelPref preferences={preferences} labelGroup="spam" /> <ContentLabelPref preferences={preferences} labelGroup="spam" />
<ContentLabelPref <ContentLabelPref
preferences={preferences} preferences={preferences}
labelGroup="impersonation" labelGroup="impersonation"
/> />
<View style={{height: isMobile ? 60 : 0}} /> <View style={{height: isMobile ? 60 : 0}} />
</ScrollView> </ScrollView>
<View <View
style={[ style={[
styles.btnContainer, styles.btnContainer,
isMobile && styles.btnContainerMobile, isMobile && styles.btnContainerMobile,
pal.borderDark, pal.borderDark,
]}> ]}>
<Pressable <Pressable
testID="sendReportBtn" testID="sendReportBtn"
onPress={onPressDone} onPress={onPressDone}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Done`)} accessibilityLabel={_(msg`Done`)}
accessibilityHint=""> accessibilityHint="">
<LinearGradient <LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]} colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}} start={{x: 0, y: 0}}
end={{x: 1, y: 1}} end={{x: 1, y: 1}}
style={[styles.btn]}> style={[styles.btn]}>
<Text style={[s.white, s.bold, s.f18]}> <Text style={[s.white, s.bold, s.f18]}>
<Trans>Done</Trans> <Trans>Done</Trans>
</Text> </Text>
</LinearGradient> </LinearGradient>
</Pressable> </Pressable>
</View>
</View> </View>
) </View>
}, )
) }
function AdultContentEnabledPref() { function AdultContentEnabledPref() {
const pal = usePalette('default') const pal = usePalette('default')
@ -171,7 +168,7 @@ function AdultContentEnabledPref() {
} }
// TODO: Refactor this component to pass labels down to each tab // TODO: Refactor this component to pass labels down to each tab
const ContentLabelPref = observer(function ContentLabelPrefImpl({ function ContentLabelPref({
preferences, preferences,
labelGroup, labelGroup,
disabled, disabled,
@ -217,7 +214,7 @@ const ContentLabelPref = observer(function ContentLabelPrefImpl({
)} )}
</View> </View>
) )
}) }
interface SelectGroupProps { interface SelectGroupProps {
current: LabelPreference current: LabelPreference

View File

@ -5,7 +5,6 @@ import {
View, View,
ActivityIndicator, ActivityIndicator,
} from 'react-native' } from 'react-native'
import {observer} from 'mobx-react-lite'
import {ComAtprotoServerDefs} from '@atproto/api' import {ComAtprotoServerDefs} from '@atproto/api'
import { import {
FontAwesomeIcon, FontAwesomeIcon,
@ -129,7 +128,7 @@ export function Inner({invites}: {invites: InviteCodesQueryResponse}) {
) )
} }
const InviteCode = observer(function InviteCodeImpl({ function InviteCode({
testID, testID,
invite, invite,
used, used,
@ -211,7 +210,7 @@ const InviteCode = observer(function InviteCodeImpl({
) : null} ) : null}
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import {Linking, SafeAreaView, StyleSheet, View} from 'react-native' import {Linking, SafeAreaView, StyleSheet, View} from 'react-native'
import {ScrollView} from './util' import {ScrollView} from './util'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
@ -16,13 +15,7 @@ import {useModalControls} from '#/state/modals'
export const snapPoints = ['50%'] export const snapPoints = ['50%']
export const Component = observer(function Component({ export function Component({text, href}: {text: string; href: string}) {
text,
href,
}: {
text: string
href: string
}) {
const pal = usePalette('default') const pal = usePalette('default')
const {closeModal} = useModalControls() const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
@ -97,7 +90,7 @@ export const Component = observer(function Component({
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
) )
}) }
function LinkBox({href}: {href: string}) { function LinkBox({href}: {href: string}) {
const pal = usePalette('default') const pal = usePalette('default')

View File

@ -1,7 +1,6 @@
import React, {useRef, useEffect} from 'react' import React, {useRef, useEffect} from 'react'
import {StyleSheet} from 'react-native' import {StyleSheet} from 'react-native'
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context' import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
import {observer} from 'mobx-react-lite'
import BottomSheet from '@gorhom/bottom-sheet' import BottomSheet from '@gorhom/bottom-sheet'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -40,7 +39,7 @@ import * as LinkWarningModal from './LinkWarning'
const DEFAULT_SNAPPOINTS = ['90%'] const DEFAULT_SNAPPOINTS = ['90%']
const HANDLE_HEIGHT = 24 const HANDLE_HEIGHT = 24
export const ModalsContainer = observer(function ModalsContainer() { export function ModalsContainer() {
const {isModalActive, activeModals} = useModals() const {isModalActive, activeModals} = useModals()
const {closeModal} = useModalControls() const {closeModal} = useModalControls()
const bottomSheetRef = useRef<BottomSheet>(null) const bottomSheetRef = useRef<BottomSheet>(null)
@ -198,7 +197,7 @@ export const ModalsContainer = observer(function ModalsContainer() {
{element} {element}
</BottomSheet> </BottomSheet>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
handle: { handle: {

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native' import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import type {Modal as ModalIface} from '#/state/modals' import type {Modal as ModalIface} from '#/state/modals'
@ -33,7 +32,7 @@ import * as VerifyEmailModal from './VerifyEmail'
import * as ChangeEmailModal from './ChangeEmail' import * as ChangeEmailModal from './ChangeEmail'
import * as LinkWarningModal from './LinkWarning' import * as LinkWarningModal from './LinkWarning'
export const ModalsContainer = observer(function ModalsContainer() { export function ModalsContainer() {
const {isModalActive, activeModals} = useModals() const {isModalActive, activeModals} = useModals()
if (!isModalActive) { if (!isModalActive) {
@ -47,7 +46,7 @@ export const ModalsContainer = observer(function ModalsContainer() {
))} ))}
</> </>
) )
}) }
function Modal({modal}: {modal: ModalIface}) { function Modal({modal}: {modal: ModalIface}) {
const {isModalActive} = useModals() const {isModalActive} = useModals()

View File

@ -1,6 +1,5 @@
import React, {useState} from 'react' import React, {useState} from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -17,7 +16,7 @@ const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
export const snapPoints = ['50%'] export const snapPoints = ['50%']
export const Component = observer(function Component({ export function Component({
labels, labels,
hasMedia, hasMedia,
onChange, onChange,
@ -161,7 +160,7 @@ export const Component = observer(function Component({
</View> </View>
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -9,7 +9,7 @@ import {Text} from '../util/text/Text'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext' 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 {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'

View File

@ -8,7 +8,6 @@ import {
} from 'react-native' } from 'react-native'
import {Svg, Circle, Path} from 'react-native-svg' import {Svg, Circle, Path} from 'react-native-svg'
import {ScrollView, TextInput} from './util' import {ScrollView, TextInput} from './util'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
@ -32,11 +31,7 @@ enum Stages {
ConfirmCode, ConfirmCode,
} }
export const Component = observer(function Component({ export function Component({showReminder}: {showReminder?: boolean}) {
showReminder,
}: {
showReminder?: boolean
}) {
const pal = usePalette('default') const pal = usePalette('default')
const {agent, currentAccount} = useSession() const {agent, currentAccount} = useSession()
const {updateCurrentAccount} = useSessionApi() const {updateCurrentAccount} = useSessionApi()
@ -244,7 +239,7 @@ export const Component = observer(function Component({
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
) )
}) }
function ReminderIllustration() { function ReminderIllustration() {
const pal = usePalette('default') const pal = usePalette('default')

View File

@ -1,11 +1,10 @@
import React from 'react' import React from 'react'
import {StyleSheet} from 'react-native' import {StyleSheet} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {observer} from 'mobx-react-lite'
import {ToggleButton} from 'view/com/util/forms/ToggleButton' import {ToggleButton} from 'view/com/util/forms/ToggleButton'
import {useLanguagePrefs, toPostLanguages} from '#/state/preferences/languages' import {useLanguagePrefs, toPostLanguages} from '#/state/preferences/languages'
export const LanguageToggle = observer(function LanguageToggleImpl({ export function LanguageToggle({
code2, code2,
name, name,
onPress, onPress,
@ -39,7 +38,7 @@ export const LanguageToggle = observer(function LanguageToggleImpl({
style={[pal.border, styles.languageToggle, isDisabled && styles.dimmed]} style={[pal.border, styles.languageToggle, isDisabled && styles.dimmed]}
/> />
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
languageToggle: { languageToggle: {

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {ScrollView} from '../util' import {ScrollView} from '../util'
import {Text} from '../../util/text/Text' import {Text} from '../../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -19,7 +18,7 @@ import {
export const snapPoints = ['100%'] export const snapPoints = ['100%']
export const Component = observer(function PostLanguagesSettingsImpl() { export function Component() {
const {closeModal} = useModalControls() const {closeModal} = useModalControls()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const setLangPrefs = useLanguagePrefsApi() const setLangPrefs = useLanguagePrefsApi()
@ -111,7 +110,7 @@ export const Component = observer(function PostLanguagesSettingsImpl() {
<ConfirmLanguagesButton onPress={onPressDone} /> <ConfirmLanguagesButton onPress={onPressDone} />
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -2,7 +2,6 @@ import React, {useState, useMemo} from 'react'
import {Linking, StyleSheet, TouchableOpacity, View} from 'react-native' import {Linking, StyleSheet, TouchableOpacity, View} from 'react-native'
import {ScrollView} from 'react-native-gesture-handler' import {ScrollView} from 'react-native-gesture-handler'
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {Text} from '../../util/text/Text' import {Text} from '../../util/text/Text'
@ -17,6 +16,7 @@ import {CollectionId} from './types'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useSession} from '#/state/session'
const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright' const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright'
@ -39,7 +39,7 @@ type ReportComponentProps =
} }
export function Component(content: ReportComponentProps) { export function Component(content: ReportComponentProps) {
const store = useStores() const {agent} = useSession()
const {closeModal} = useModalControls() const {closeModal} = useModalControls()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
@ -70,7 +70,7 @@ export function Component(content: ReportComponentProps) {
const $type = !isAccountReport const $type = !isAccountReport
? 'com.atproto.repo.strongRef' ? 'com.atproto.repo.strongRef'
: 'com.atproto.admin.defs#repoRef' : 'com.atproto.admin.defs#repoRef'
await store.agent.createModerationReport({ await agent.createModerationReport({
reasonType: issue, reasonType: issue,
subject: { subject: {
$type, $type,

View File

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import {StyleSheet} from 'react-native' import {StyleSheet} from 'react-native'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
import {observer} from 'mobx-react-lite'
import {TabBar} from 'view/com/pager/TabBar' import {TabBar} from 'view/com/pager/TabBar'
import {RenderTabBarFnProps} from 'view/com/pager/Pager' import {RenderTabBarFnProps} from 'view/com/pager/Pager'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -11,7 +10,7 @@ import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {useShellLayout} from '#/state/shell/shell-layout' import {useShellLayout} from '#/state/shell/shell-layout'
import {usePinnedFeedsInfos} from '#/state/queries/feed' import {usePinnedFeedsInfos} from '#/state/queries/feed'
export const FeedsTabBar = observer(function FeedsTabBarImpl( export function FeedsTabBar(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) { ) {
const {isMobile, isTablet} = useWebMediaQueries() const {isMobile, isTablet} = useWebMediaQueries()
@ -22,9 +21,9 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl(
} else { } else {
return null return null
} }
}) }
const FeedsTabBarTablet = observer(function FeedsTabBarTabletImpl( function FeedsTabBarTablet(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) { ) {
const feeds = usePinnedFeedsInfos() const feeds = usePinnedFeedsInfos()
@ -48,7 +47,7 @@ const FeedsTabBarTablet = observer(function FeedsTabBarTabletImpl(
/> />
</Animated.View> </Animated.View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tabBar: { tabBar: {

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {TabBar} from 'view/com/pager/TabBar' import {TabBar} from 'view/com/pager/TabBar'
import {RenderTabBarFnProps} from 'view/com/pager/Pager' import {RenderTabBarFnProps} from 'view/com/pager/Pager'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -20,7 +19,7 @@ import {useShellLayout} from '#/state/shell/shell-layout'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {usePinnedFeedsInfos} from '#/state/queries/feed' import {usePinnedFeedsInfos} from '#/state/queries/feed'
export const FeedsTabBar = observer(function FeedsTabBarImpl( export function FeedsTabBar(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) { ) {
const pal = usePalette('default') const pal = usePalette('default')
@ -88,7 +87,7 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl(
/> />
</Animated.View> </Animated.View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tabBar: { tabBar: {

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {FeedPostSlice} from '#/state/queries/post-feed' import {FeedPostSlice} from '#/state/queries/post-feed'
import {AtUri, moderatePost, ModerationOpts} from '@atproto/api' import {AtUri, moderatePost, ModerationOpts} from '@atproto/api'
import {Link} from '../util/Link' import {Link} from '../util/Link'
@ -10,7 +9,7 @@ import {FeedItem} from './FeedItem'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
export const FeedSlice = observer(function FeedSliceImpl({ export function FeedSlice({
slice, slice,
dataUpdatedAt, dataUpdatedAt,
ignoreFilterFor, ignoreFilterFor,
@ -94,7 +93,7 @@ export const FeedSlice = observer(function FeedSliceImpl({
))} ))}
</> </>
) )
}) }
function ViewFullThread({slice}: {slice: FeedPostSlice}) { function ViewFullThread({slice}: {slice: FeedPostSlice}) {
const pal = usePalette('default') const pal = usePalette('default')

View File

@ -1,6 +1,5 @@
import * as React from 'react' import * as React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {observer} from 'mobx-react-lite'
import { import {
AppBskyActorDefs, AppBskyActorDefs,
moderateProfile, moderateProfile,
@ -152,7 +151,7 @@ function ProfileCardPills({
) )
} }
const FollowersList = observer(function FollowersListImpl({ function FollowersList({
followers, followers,
}: { }: {
followers?: AppBskyActorDefs.ProfileView[] | undefined followers?: AppBskyActorDefs.ProfileView[] | undefined
@ -196,7 +195,7 @@ const FollowersList = observer(function FollowersListImpl({
))} ))}
</View> </View>
) )
}) }
export function ProfileCardWithFollowBtn({ export function ProfileCardWithFollowBtn({
profile, profile,

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {Pressable, StyleSheet, View} from 'react-native' import {Pressable, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -12,7 +11,6 @@ import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {CenteredView} from '../util/Views' import {CenteredView} from '../util/Views'
import {sanitizeHandle} from 'lib/strings/handles' import {sanitizeHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {useStores} from 'state/index'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {BACK_HITSLOP} from 'lib/constants' import {BACK_HITSLOP} from 'lib/constants'
import {isNative} from 'platform/detection' import {isNative} from 'platform/detection'
@ -22,7 +20,7 @@ import {msg} from '@lingui/macro'
import {useSetDrawerOpen} from '#/state/shell' import {useSetDrawerOpen} from '#/state/shell'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
export const ProfileSubpageHeader = observer(function HeaderImpl({ export function ProfileSubpageHeader({
isLoading, isLoading,
href, href,
title, title,
@ -45,7 +43,6 @@ export const ProfileSubpageHeader = observer(function HeaderImpl({
| undefined | undefined
avatarType: UserAvatarType avatarType: UserAvatarType
}>) { }>) {
const store = useStores()
const setDrawerOpen = useSetDrawerOpen() const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {_} = useLingui() const {_} = useLingui()
@ -183,7 +180,7 @@ export const ProfileSubpageHeader = observer(function HeaderImpl({
</View> </View>
</CenteredView> </CenteredView>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
backBtn: { backBtn: {

View File

@ -6,7 +6,6 @@ import {niceDate} from 'lib/strings/time'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {TypographyVariant} from 'lib/ThemeContext' import {TypographyVariant} from 'lib/ThemeContext'
import {UserAvatar} from './UserAvatar' import {UserAvatar} from './UserAvatar'
import {observer} from 'mobx-react-lite'
import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles' import {sanitizeHandle} from 'lib/strings/handles'
import {isAndroid} from 'platform/detection' import {isAndroid} from 'platform/detection'
@ -30,7 +29,7 @@ interface PostMetaOpts {
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
} }
export const PostMeta = observer(function PostMetaImpl(opts: PostMetaOpts) { export function PostMeta(opts: PostMetaOpts) {
const pal = usePalette('default') const pal = usePalette('default')
const displayName = opts.author.displayName || opts.author.handle const displayName = opts.author.displayName || opts.author.handle
const handle = opts.author.handle const handle = opts.author.handle
@ -92,7 +91,7 @@ export const PostMeta = observer(function PostMetaImpl(opts: PostMetaOpts) {
</TimeElapsed> </TimeElapsed>
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import {observer} from 'mobx-react-lite'
import { import {
StyleProp, StyleProp,
StyleSheet, StyleSheet,
@ -18,7 +17,7 @@ import {useSetDrawerOpen} from '#/state/shell'
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
export const SimpleViewHeader = observer(function SimpleViewHeaderImpl({ export function SimpleViewHeader({
showBackButton = true, showBackButton = true,
style, style,
children, children,
@ -76,7 +75,7 @@ export const SimpleViewHeader = observer(function SimpleViewHeaderImpl({
{children} {children}
</Container> </Container>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
header: { header: {

View File

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import {observer} from 'mobx-react-lite'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useNavigation} from '@react-navigation/native' 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} const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
export const ViewHeader = observer(function ViewHeaderImpl({ export function ViewHeader({
title, title,
canGoBack, canGoBack,
showBackButton = true, showBackButton = true,
@ -108,7 +107,7 @@ export const ViewHeader = observer(function ViewHeaderImpl({
</Container> </Container>
) )
} }
}) }
function DesktopWebHeader({ function DesktopWebHeader({
title, title,
@ -140,7 +139,7 @@ function DesktopWebHeader({
) )
} }
const Container = observer(function ContainerImpl({ function Container({
children, children,
hideOnScroll, hideOnScroll,
showBorder, showBorder,
@ -178,7 +177,7 @@ const Container = observer(function ContainerImpl({
{children} {children}
</Animated.View> </Animated.View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
header: { header: {

View File

@ -1,5 +1,4 @@
import React, {ComponentProps} from 'react' import React, {ComponentProps} from 'react'
import {observer} from 'mobx-react-lite'
import {StyleSheet, TouchableWithoutFeedback} from 'react-native' import {StyleSheet, TouchableWithoutFeedback} from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {gradients} from 'lib/styles' import {gradients} from 'lib/styles'
@ -15,11 +14,7 @@ export interface FABProps
icon: JSX.Element icon: JSX.Element
} }
export const FABInner = observer(function FABInnerImpl({ export function FABInner({testID, icon, ...props}: FABProps) {
testID,
icon,
...props
}: FABProps) {
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const {isMobile, isTablet} = useWebMediaQueries() const {isMobile, isTablet} = useWebMediaQueries()
const {fabMinimalShellTransform} = useMinimalShellMode() const {fabMinimalShellTransform} = useMinimalShellMode()
@ -55,7 +50,7 @@ export const FABInner = observer(function FABInnerImpl({
</Animated.View> </Animated.View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
sizeRegular: { sizeRegular: {

View File

@ -2,8 +2,8 @@ import React from 'react'
import {StyleProp, StyleSheet, Pressable, View, ViewStyle} from 'react-native' import {StyleProp, StyleSheet, Pressable, View, ViewStyle} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {clamp} from 'lib/numbers' import {clamp} from 'lib/numbers'
import {useStores} from 'state/index'
import {Dimensions} from 'lib/media/types' import {Dimensions} from 'lib/media/types'
import * as imageSizes from 'lib/media/image-sizes'
const MIN_ASPECT_RATIO = 0.33 // 1/3 const MIN_ASPECT_RATIO = 0.33 // 1/3
const MAX_ASPECT_RATIO = 5 // 5/1 const MAX_ASPECT_RATIO = 5 // 5/1
@ -29,9 +29,8 @@ export function AutoSizedImage({
style, style,
children = null, children = null,
}: Props) { }: Props) {
const store = useStores()
const [dim, setDim] = React.useState<Dimensions | undefined>( const [dim, setDim] = React.useState<Dimensions | undefined>(
dimensionsHint || store.imageSizes.get(uri), dimensionsHint || imageSizes.get(uri),
) )
const [aspectRatio, setAspectRatio] = React.useState<number>( const [aspectRatio, setAspectRatio] = React.useState<number>(
dim ? calc(dim) : 1, dim ? calc(dim) : 1,
@ -41,14 +40,14 @@ export function AutoSizedImage({
if (dim) { if (dim) {
return return
} }
store.imageSizes.fetch(uri).then(newDim => { imageSizes.fetch(uri).then(newDim => {
if (aborted) { if (aborted) {
return return
} }
setDim(newDim) setDim(newDim)
setAspectRatio(calc(newDim)) setAspectRatio(calc(newDim))
}) })
}, [dim, setDim, setAspectRatio, store, uri]) }, [dim, setDim, setAspectRatio, uri])
if (onPress || onLongPress || onPressIn) { if (onPress || onLongPress || onPressIn) {
return ( return (

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@ -12,7 +11,7 @@ const AnimatedTouchableOpacity =
Animated.createAnimatedComponent(TouchableOpacity) Animated.createAnimatedComponent(TouchableOpacity)
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
export const LoadLatestBtn = observer(function LoadLatestBtnImpl({ export function LoadLatestBtn({
onPress, onPress,
label, label,
showIndicator, showIndicator,
@ -44,7 +43,7 @@ export const LoadLatestBtn = observer(function LoadLatestBtnImpl({
{showIndicator && <View style={[styles.indicator, pal.borderDark]} />} {showIndicator && <View style={[styles.indicator, pal.borderDark]} />}
</AnimatedTouchableOpacity> </AnimatedTouchableOpacity>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
loadLatest: { loadLatest: {

View File

@ -1,12 +1,11 @@
import React from 'react' import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {observer} from 'mobx-react-lite'
import {ListCard} from 'view/com/lists/ListCard' import {ListCard} from 'view/com/lists/ListCard'
import {AppBskyGraphDefs} from '@atproto/api' import {AppBskyGraphDefs} from '@atproto/api'
import {s} from 'lib/styles' import {s} from 'lib/styles'
export const ListEmbed = observer(function ListEmbedImpl({ export function ListEmbed({
item, item,
style, style,
}: { }: {
@ -20,7 +19,7 @@ export const ListEmbed = observer(function ListEmbedImpl({
<ListCard list={item} style={[style, styles.card]} /> <ListCard list={item} style={[style, styles.card]} />
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {observer} from 'mobx-react-lite'
import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types' import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed' import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {withAuthRequired} from 'view/com/auth/withAuthRequired'
@ -15,130 +14,126 @@ import {usePreferencesQuery} from '#/state/queries/preferences'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
export const HomeScreen = withAuthRequired( export const HomeScreen = withAuthRequired(function HomeScreenImpl({}: Props) {
observer(function HomeScreenImpl({}: Props) { const setMinimalShellMode = useSetMinimalShellMode()
const setMinimalShellMode = useSetMinimalShellMode() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() const pagerRef = React.useRef<PagerRef>(null)
const pagerRef = React.useRef<PagerRef>(null) const [selectedPage, setSelectedPage] = React.useState(0)
const [selectedPage, setSelectedPage] = React.useState(0) const [customFeeds, setCustomFeeds] = React.useState<FeedDescriptor[]>([])
const [customFeeds, setCustomFeeds] = React.useState<FeedDescriptor[]>([]) const {data: preferences} = usePreferencesQuery()
const {data: preferences} = usePreferencesQuery()
React.useEffect(() => { React.useEffect(() => {
if (!preferences?.feeds?.pinned) return 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) { for (const uri of pinned) {
if (uri.includes('app.bsky.feed.generator')) { if (uri.includes('app.bsky.feed.generator')) {
feeds.push(`feedgen|${uri}`) feeds.push(`feedgen|${uri}`)
} else if (uri.includes('app.bsky.graph.list')) { } else if (uri.includes('app.bsky.graph.list')) {
feeds.push(`list|${uri}`) feeds.push(`list|${uri}`)
}
} }
}
setCustomFeeds(feeds) setCustomFeeds(feeds)
pagerRef.current?.setPage(0) pagerRef.current?.setPage(0)
}, [preferences?.feeds?.pinned, setCustomFeeds, pagerRef]) }, [preferences?.feeds?.pinned, setCustomFeeds, pagerRef])
const homeFeedParams = React.useMemo<FeedParams>(() => { const homeFeedParams = React.useMemo<FeedParams>(() => {
if (!preferences) return {} if (!preferences) return {}
return { return {
mergeFeedEnabled: Boolean( mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled),
preferences.feedViewPrefs.lab_mergeFeedEnabled, mergeFeedSources: preferences.feeds.saved,
), }
mergeFeedSources: preferences.feeds.saved, }, [preferences])
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
setDrawerSwipeDisabled(selectedPage > 0)
return () => {
setDrawerSwipeDisabled(false)
} }
}, [preferences]) }, [setDrawerSwipeDisabled, selectedPage, setMinimalShellMode]),
)
useFocusEffect( const onPageSelected = React.useCallback(
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) setMinimalShellMode(false)
setDrawerSwipeDisabled(selectedPage > 0) }
return () => { },
setDrawerSwipeDisabled(false) [setMinimalShellMode],
} )
}, [setDrawerSwipeDisabled, selectedPage, setMinimalShellMode]),
)
const onPageSelected = React.useCallback( const renderTabBar = React.useCallback(
(index: number) => { (props: RenderTabBarFnProps) => {
setMinimalShellMode(false) return (
setSelectedPage(index) <FeedsTabBar
setDrawerSwipeDisabled(index > 0) key="FEEDS_TAB_BAR"
}, selectedPage={props.selectedPage}
[setDrawerSwipeDisabled, setSelectedPage, setMinimalShellMode], onSelect={props.onSelect}
) testID="homeScreenFeedTabs"
onPressSelected={onPressSelected}
/>
)
},
[onPressSelected],
)
const onPressSelected = React.useCallback(() => { const renderFollowingEmptyState = React.useCallback(() => {
emitSoftReset() return <FollowingEmptyState />
}, []) }, [])
const onPageScrollStateChanged = React.useCallback( const renderCustomFeedEmptyState = React.useCallback(() => {
(state: 'idle' | 'dragging' | 'settling') => { return <CustomFeedEmptyState />
if (state === 'dragging') { }, [])
setMinimalShellMode(false)
}
},
[setMinimalShellMode],
)
const renderTabBar = React.useCallback( return (
(props: RenderTabBarFnProps) => { <Pager
ref={pagerRef}
testID="homeScreen"
onPageSelected={onPageSelected}
onPageScrollStateChanged={onPageScrollStateChanged}
renderTabBar={renderTabBar}
tabBarPosition="top">
<FeedPage
key="1"
testID="followingFeedPage"
isPageFocused={selectedPage === 0}
feed="home"
feedParams={homeFeedParams}
renderEmptyState={renderFollowingEmptyState}
renderEndOfFeed={FollowingEndOfFeed}
/>
{customFeeds.map((f, index) => {
return ( return (
<FeedsTabBar <FeedPage
key="FEEDS_TAB_BAR" key={f}
selectedPage={props.selectedPage} testID="customFeedPage"
onSelect={props.onSelect} isPageFocused={selectedPage === 1 + index}
testID="homeScreenFeedTabs" feed={f}
onPressSelected={onPressSelected} renderEmptyState={renderCustomFeedEmptyState}
/> />
) )
}, })}
[onPressSelected], </Pager>
) )
})
const renderFollowingEmptyState = React.useCallback(() => {
return <FollowingEmptyState />
}, [])
const renderCustomFeedEmptyState = React.useCallback(() => {
return <CustomFeedEmptyState />
}, [])
return (
<Pager
ref={pagerRef}
testID="homeScreen"
onPageSelected={onPageSelected}
onPageScrollStateChanged={onPageScrollStateChanged}
renderTabBar={renderTabBar}
tabBarPosition="top">
<FeedPage
key="1"
testID="followingFeedPage"
isPageFocused={selectedPage === 0}
feed="home"
feedParams={homeFeedParams}
renderEmptyState={renderFollowingEmptyState}
renderEndOfFeed={FollowingEndOfFeed}
/>
{customFeeds.map((f, index) => {
return (
<FeedPage
key={f}
testID="customFeedPage"
isPageFocused={selectedPage === 1 + index}
feed={f}
renderEmptyState={renderCustomFeedEmptyState}
/>
)
})}
</Pager>
)
}),
)

View File

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Text} from '../com/util/text/Text' import {Text} from '../com/util/text/Text'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -23,9 +22,7 @@ import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
export const LanguageSettingsScreen = observer(function LanguageSettingsImpl( export function LanguageSettingsScreen(_: Props) {
_: Props,
) {
const pal = usePalette('default') const pal = usePalette('default')
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const setLangPrefs = useLanguagePrefsApi() const setLangPrefs = useLanguagePrefsApi()
@ -192,7 +189,7 @@ export const LanguageSettingsScreen = observer(function LanguageSettingsImpl(
</View> </View>
</CenteredView> </CenteredView>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {ScrollView} from '../com/util/Views' import {ScrollView} from '../com/util/Views'
@ -15,7 +14,7 @@ import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
export const LogScreen = observer(function Log({}: NativeStackScreenProps< export function LogScreen({}: NativeStackScreenProps<
CommonNavigatorParams, CommonNavigatorParams,
'Log' 'Log'
>) { >) {
@ -88,7 +87,7 @@ export const LogScreen = observer(function Log({}: NativeStackScreenProps<
</ScrollView> </ScrollView>
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
entry: { entry: {

View File

@ -5,7 +5,6 @@ import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import {observer} from 'mobx-react-lite'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {s} from 'lib/styles' import {s} from 'lib/styles'
@ -21,7 +20,7 @@ import {useModalControls} from '#/state/modals'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>
export const ModerationScreen = withAuthRequired( export const ModerationScreen = withAuthRequired(
observer(function Moderation({}: Props) { function Moderation({}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {screen, track} = useAnalytics() const {screen, track} = useAnalytics()
@ -111,7 +110,7 @@ export const ModerationScreen = withAuthRequired(
</Link> </Link>
</CenteredView> </CenteredView>
) )
}), },
) )
const styles = StyleSheet.create({ const styles = StyleSheet.create({

View File

@ -3,7 +3,6 @@ import {StyleSheet, View} from 'react-native'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {observer} from 'mobx-react-lite'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {makeRecordUri} from 'lib/strings/url-helpers' import {makeRecordUri} from 'lib/strings/url-helpers'
import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {withAuthRequired} from 'view/com/auth/withAuthRequired'
@ -26,83 +25,83 @@ import {CenteredView} from '../com/util/Views'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
export const PostThreadScreen = withAuthRequired( export const PostThreadScreen = withAuthRequired(function PostThreadScreenImpl({
observer(function PostThreadScreenImpl({route}: Props) { route,
const queryClient = useQueryClient() }: Props) {
const {fabMinimalShellTransform} = useMinimalShellMode() const queryClient = useQueryClient()
const setMinimalShellMode = useSetMinimalShellMode() const {fabMinimalShellTransform} = useMinimalShellMode()
const {openComposer} = useComposerControls() const setMinimalShellMode = useSetMinimalShellMode()
const safeAreaInsets = useSafeAreaInsets() const {openComposer} = useComposerControls()
const {name, rkey} = route.params const safeAreaInsets = useSafeAreaInsets()
const {isMobile} = useWebMediaQueries() const {name, rkey} = route.params
const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) const {isMobile} = useWebMediaQueries()
const {data: resolvedUri, error: uriError} = useResolveUriQuery(uri) const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
const {data: resolvedUri, error: uriError} = useResolveUriQuery(uri)
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
setMinimalShellMode(false) setMinimalShellMode(false)
}, [setMinimalShellMode]), }, [setMinimalShellMode]),
)
const onPressReply = React.useCallback(() => {
if (!resolvedUri) {
return
}
const thread = queryClient.getQueryData<ThreadNode>(
POST_THREAD_RQKEY(resolvedUri.uri),
) )
if (thread?.type !== 'post') {
const onPressReply = React.useCallback(() => { return
if (!resolvedUri) { }
return openComposer({
} replyTo: {
const thread = queryClient.getQueryData<ThreadNode>( uri: thread.post.uri,
POST_THREAD_RQKEY(resolvedUri.uri), cid: thread.post.cid,
) text: thread.record.text,
if (thread?.type !== 'post') { author: {
return handle: thread.post.author.handle,
} displayName: thread.post.author.displayName,
openComposer({ avatar: thread.post.author.avatar,
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({ onPost: () =>
queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''), queryClient.invalidateQueries({
}), queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''),
}) }),
}, [openComposer, queryClient, resolvedUri]) })
}, [openComposer, queryClient, resolvedUri])
return ( return (
<View style={s.hContentRegion}> <View style={s.hContentRegion}>
{isMobile && <ViewHeader title="Post" />} {isMobile && <ViewHeader title="Post" />}
<View style={s.flex1}> <View style={s.flex1}>
{uriError ? ( {uriError ? (
<CenteredView> <CenteredView>
<ErrorMessage message={String(uriError)} /> <ErrorMessage message={String(uriError)} />
</CenteredView> </CenteredView>
) : ( ) : (
<PostThreadComponent <PostThreadComponent
uri={resolvedUri?.uri} uri={resolvedUri?.uri}
onPressReply={onPressReply} onPressReply={onPressReply}
/> />
)}
</View>
{isMobile && (
<Animated.View
style={[
styles.prompt,
fabMinimalShellTransform,
{
bottom: clamp(safeAreaInsets.bottom, 15, 30),
},
]}>
<ComposePrompt onPressCompose={onPressReply} />
</Animated.View>
)} )}
</View> </View>
) {isMobile && (
}), <Animated.View
) style={[
styles.prompt,
fabMinimalShellTransform,
{
bottom: clamp(safeAreaInsets.bottom, 15, 30),
},
]}>
<ComposePrompt onPressCompose={onPressReply} />
</Animated.View>
)}
</View>
)
})
const styles = StyleSheet.create({ const styles = StyleSheet.create({
prompt: { prompt: {

View File

@ -1,6 +1,5 @@
import React, {useState} from 'react' import React, {useState} from 'react'
import {ScrollView, StyleSheet, TouchableOpacity, View} from 'react-native' import {ScrollView, StyleSheet, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Slider} from '@miblanchard/react-native-slider' import {Slider} from '@miblanchard/react-native-slider'
import {Text} from '../com/util/text/Text' import {Text} from '../com/util/text/Text'
@ -72,9 +71,7 @@ type Props = NativeStackScreenProps<
CommonNavigatorParams, CommonNavigatorParams,
'PreferencesHomeFeed' 'PreferencesHomeFeed'
> >
export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({ export function PreferencesHomeFeed({navigation}: Props) {
navigation,
}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {isTabletOrDesktop} = useWebMediaQueries() const {isTabletOrDesktop} = useWebMediaQueries()
@ -308,7 +305,7 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
</View> </View>
</CenteredView> </CenteredView>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -6,7 +6,6 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native' } from 'react-native'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../com/util/text/Text' import {Text} from '../com/util/text/Text'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
@ -25,9 +24,7 @@ import {
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PreferencesThreads'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'PreferencesThreads'>
export const PreferencesThreads = observer(function PreferencesThreadsImpl({ export function PreferencesThreads({navigation}: Props) {
navigation,
}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {isTabletOrDesktop} = useWebMediaQueries() const {isTabletOrDesktop} = useWebMediaQueries()
@ -162,7 +159,7 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
</View> </View>
</CenteredView> </CenteredView>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -15,7 +15,6 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {CommonNavigatorParams} from 'lib/routes/types' import {CommonNavigatorParams} from 'lib/routes/types'
import {makeRecordUri} from 'lib/strings/url-helpers' import {makeRecordUri} from 'lib/strings/url-helpers'
import {colors, s} from 'lib/styles' import {colors, s} from 'lib/styles'
import {observer} from 'mobx-react-lite'
import {FeedDescriptor} from '#/state/queries/post-feed' import {FeedDescriptor} from '#/state/queries/post-feed'
import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
@ -71,7 +70,7 @@ interface SectionRef {
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'>
export const ProfileFeedScreen = withAuthRequired( export const ProfileFeedScreen = withAuthRequired(
observer(function ProfileFeedScreenImpl(props: Props) { function ProfileFeedScreenImpl(props: Props) {
const {rkey, name: handleOrDid} = props.route.params const {rkey, name: handleOrDid} = props.route.params
const pal = usePalette('default') const pal = usePalette('default')
@ -129,7 +128,7 @@ export const ProfileFeedScreen = withAuthRequired(
</View> </View>
</CenteredView> </CenteredView>
) )
}), },
) )
function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) { function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
@ -154,7 +153,7 @@ function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
) )
} }
export const ProfileFeedScreenInner = function ProfileFeedScreenInnerImpl({ export function ProfileFeedScreenInner({
preferences, preferences,
feedInfo, feedInfo,
}: { }: {
@ -485,7 +484,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
}, },
) )
const AboutSection = observer(function AboutPageImpl({ function AboutSection({
feedOwnerDid, feedOwnerDid,
feedRkey, feedRkey,
feedInfo, feedInfo,
@ -606,7 +605,7 @@ const AboutSection = observer(function AboutPageImpl({
</View> </View>
</ScrollView> </ScrollView>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
btn: { btn: {

View File

@ -14,7 +14,6 @@ import {track} from '#/lib/analytics/analytics'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {CommonNavigatorParams} from 'lib/routes/types' import {CommonNavigatorParams} from 'lib/routes/types'
import {observer} from 'mobx-react-lite'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {ViewHeader} from 'view/com/util/ViewHeader' 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, feedUri,
isPinned, isPinned,
}: { }: {
@ -269,7 +268,7 @@ const ListItem = observer(function ListItemImpl({
</TouchableOpacity> </TouchableOpacity>
</Pressable> </Pressable>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
desktopContainer: { desktopContainer: {

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,6 @@ import {
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {useNavigation, StackActions} from '@react-navigation/native' import {useNavigation, StackActions} from '@react-navigation/native'
import {observer} from 'mobx-react-lite'
import { import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, FontAwesomeIconStyle,
@ -101,7 +100,7 @@ export function DrawerProfileCard({
) )
} }
export const DrawerContent = observer(function DrawerContentImpl() { export function DrawerContent() {
const theme = useTheme() const theme = useTheme()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
@ -404,7 +403,7 @@ export const DrawerContent = observer(function DrawerContentImpl() {
</SafeAreaView> </SafeAreaView>
</View> </View>
) )
}) }
interface MenuItemProps extends ComponentProps<typeof TouchableOpacity> { interface MenuItemProps extends ComponentProps<typeof TouchableOpacity> {
icon: JSX.Element icon: JSX.Element
@ -458,11 +457,7 @@ function MenuItem({
) )
} }
const InviteCodes = observer(function InviteCodesImpl({ function InviteCodes({style}: {style?: StyleProp<ViewStyle>}) {
style,
}: {
style?: StyleProp<ViewStyle>
}) {
const {track} = useAnalytics() const {track} = useAnalytics()
const setDrawerOpen = useSetDrawerOpen() const setDrawerOpen = useSetDrawerOpen()
const pal = usePalette('default') const pal = usePalette('default')
@ -502,7 +497,7 @@ const InviteCodes = observer(function InviteCodesImpl({
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
view: { view: {

View File

@ -4,7 +4,6 @@ import Animated from 'react-native-reanimated'
import {StackActions} from '@react-navigation/native' import {StackActions} from '@react-navigation/native'
import {BottomTabBarProps} from '@react-navigation/bottom-tabs' import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {observer} from 'mobx-react-lite'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {clamp} from 'lib/numbers' import {clamp} from 'lib/numbers'
@ -34,9 +33,7 @@ import {useProfileQuery} from '#/state/queries/profile'
type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds' type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds'
export const BottomBar = observer(function BottomBarImpl({ export function BottomBar({navigation}: BottomTabBarProps) {
navigation,
}: BottomTabBarProps) {
const {openModal} = useModalControls() const {openModal} = useModalControls()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const pal = usePalette('default') const pal = usePalette('default')
@ -231,7 +228,7 @@ export const BottomBar = observer(function BottomBarImpl({
/> />
</Animated.View> </Animated.View>
) )
}) }
interface BtnProps interface BtnProps
extends Pick< extends Pick<

View File

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import {observer} from 'mobx-react-lite'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useNavigationState} from '@react-navigation/native' import {useNavigationState} from '@react-navigation/native'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
@ -24,7 +23,7 @@ import {makeProfileLink} from 'lib/routes/links'
import {CommonNavigatorParams} from 'lib/routes/types' import {CommonNavigatorParams} from 'lib/routes/types'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
export const BottomBarWeb = observer(function BottomBarWebImpl() { export function BottomBarWeb() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const pal = usePalette('default') const pal = usePalette('default')
const safeAreaInsets = useSafeAreaInsets() const safeAreaInsets = useSafeAreaInsets()
@ -111,7 +110,7 @@ export const BottomBarWeb = observer(function BottomBarWebImpl() {
</NavItem> </NavItem>
</Animated.View> </Animated.View>
) )
}) }
const NavItem: React.FC<{ const NavItem: React.FC<{
children: (props: {isActive: boolean}) => React.ReactChild children: (props: {isActive: boolean}) => React.ReactChild

View File

@ -1,13 +1,12 @@
import React from 'react' import React from 'react'
import {View, StyleSheet} from 'react-native' import {View, StyleSheet} from 'react-native'
import {useNavigationState} from '@react-navigation/native' import {useNavigationState} from '@react-navigation/native'
import {observer} from 'mobx-react-lite'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {TextLink} from 'view/com/util/Link' import {TextLink} from 'view/com/util/Link'
import {getCurrentRoute} from 'lib/routes/helpers' import {getCurrentRoute} from 'lib/routes/helpers'
import {usePinnedFeedsInfos} from '#/state/queries/feed' import {usePinnedFeedsInfos} from '#/state/queries/feed'
export const DesktopFeeds = observer(function DesktopFeeds() { export function DesktopFeeds() {
const pal = usePalette('default') const pal = usePalette('default')
const feeds = usePinnedFeedsInfos() const feeds = usePinnedFeedsInfos()
@ -54,7 +53,7 @@ export const DesktopFeeds = observer(function DesktopFeeds() {
</View> </View>
</View> </View>
) )
}) }
function FeedItem({ function FeedItem({
title, title,

View File

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import {observer} from 'mobx-react-lite'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {PressableWithHover} from 'view/com/util/PressableWithHover' import {PressableWithHover} from 'view/com/util/PressableWithHover'
import { import {
@ -47,7 +46,7 @@ import {useComposerControls} from '#/state/shell/composer'
import {useFetchHandle} from '#/state/queries/handle' import {useFetchHandle} from '#/state/queries/handle'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
const ProfileCard = observer(function ProfileCardImpl() { function ProfileCard() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {isLoading, data: profile} = useProfileQuery({did: currentAccount!.did}) const {isLoading, data: profile} = useProfileQuery({did: currentAccount!.did})
const {isDesktop} = useWebMediaQueries() const {isDesktop} = useWebMediaQueries()
@ -73,7 +72,7 @@ const ProfileCard = observer(function ProfileCardImpl() {
/> />
</View> </View>
) )
}) }
function BackBtn() { function BackBtn() {
const {isTablet} = useWebMediaQueries() const {isTablet} = useWebMediaQueries()
@ -117,13 +116,7 @@ interface NavItemProps {
iconFilled: JSX.Element iconFilled: JSX.Element
label: string label: string
} }
const NavItem = observer(function NavItemImpl({ function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
count,
href,
icon,
iconFilled,
label,
}: NavItemProps) {
const pal = usePalette('default') const pal = usePalette('default')
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {isDesktop, isTablet} = useWebMediaQueries() const {isDesktop, isTablet} = useWebMediaQueries()
@ -192,7 +185,7 @@ const NavItem = observer(function NavItemImpl({
)} )}
</PressableWithHover> </PressableWithHover>
) )
}) }
function ComposeBtn() { function ComposeBtn() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
@ -264,7 +257,7 @@ function ComposeBtn() {
) )
} }
export const DesktopLeftNav = observer(function DesktopLeftNav() { export function DesktopLeftNav() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const pal = usePalette('default') const pal = usePalette('default')
const {isDesktop, isTablet} = useWebMediaQueries() const {isDesktop, isTablet} = useWebMediaQueries()
@ -422,7 +415,7 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() {
<ComposeBtn /> <ComposeBtn />
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
leftNav: { leftNav: {

View File

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import {observer} from 'mobx-react-lite'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -16,7 +15,7 @@ import {useModalControls} from '#/state/modals'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useInviteCodesQuery} from '#/state/queries/invites' import {useInviteCodesQuery} from '#/state/queries/invites'
export const DesktopRightNav = observer(function DesktopRightNavImpl() { export function DesktopRightNav() {
const pal = usePalette('default') const pal = usePalette('default')
const palError = usePalette('error') const palError = usePalette('error')
const {isSandbox, hasSession, currentAccount} = useSession() const {isSandbox, hasSession, currentAccount} = useSession()
@ -80,9 +79,9 @@ export const DesktopRightNav = observer(function DesktopRightNavImpl() {
<InviteCodes /> <InviteCodes />
</View> </View>
) )
}) }
const InviteCodes = observer(function InviteCodesImpl() { function InviteCodes() {
const pal = usePalette('default') const pal = usePalette('default')
const {openModal} = useModalControls() const {openModal} = useModalControls()
const {data: invites} = useInviteCodesQuery() const {data: invites} = useInviteCodesQuery()
@ -118,7 +117,7 @@ const InviteCodes = observer(function InviteCodesImpl() {
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
rightNav: { rightNav: {

View File

@ -1,5 +1,4 @@
import React, {useEffect} from 'react' import React, {useEffect} from 'react'
import {observer} from 'mobx-react-lite'
import {View, StyleSheet, TouchableOpacity} from 'react-native' import {View, StyleSheet, TouchableOpacity} from 'react-native'
import {DesktopLeftNav} from './desktop/LeftNav' import {DesktopLeftNav} from './desktop/LeftNav'
import {DesktopRightNav} from './desktop/RightNav' 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) const pageBg = useColorSchemeStyle(styles.bgLight, styles.bgDark)
return ( return (
<View style={[s.hContentRegion, pageBg]}> <View style={[s.hContentRegion, pageBg]}>
@ -85,7 +84,7 @@ export const Shell: React.FC = observer(function ShellImpl() {
</RoutesContainer> </RoutesContainer>
</View> </View>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
bgLight: { bgLight: {