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 wrapperszio/stable
parent
e637798e05
commit
54faa7e176
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
||||||
|
|
|
@ -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},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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'
|
|
|
@ -1,5 +0,0 @@
|
||||||
import {LRUMap} from 'lru_map'
|
|
||||||
|
|
||||||
export class HandleResolutionsCache {
|
|
||||||
cache: LRUMap<string, string> = new LRUMap(500)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
|
@ -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() {}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
import {SessionModel} from '../models/session'
|
export function shouldRequestEmailConfirmation() {
|
||||||
|
|
||||||
export function shouldRequestEmailConfirmation(_session: SessionModel) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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
|
@ -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: {
|
||||||
|
|
|
@ -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<
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
Loading…
Reference in New Issue