finish phase 3: type-only codemod and zero non-bridge @atproto/api imports

Task 8: codemod repointed 150 namespace-type files to #/lexicons
(AppBskyFeedDefs.PostView -> app.bsky.feed.defs.PostView, .Record -> .Main)
plus ~105 hand-fixed consumers (composer/video state, DebugMod, Profile,
onboarding, dialogs). DM_SERVICE_HEADERS deleted; last chat call sites on
the chat client. RichText.tsx flipped to SDK-only. Remaining @atproto/api
importers: the three session bridge files, the deliberate dual-world
matcher in lib/xrpc-error.ts, and test fixtures. toLex boundary casts are
TODO(phase4)-tagged for bridge removal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-07-17 02:08:48 +03:00
parent 300a50b69a
commit e7d349bc30
283 changed files with 2029 additions and 1806 deletions
+6 -12
View File
@@ -1,8 +1,3 @@
import {
ageAssuranceRuleIDs as ids,
type AppBskyAgeassuranceDefs,
} from '@atproto/api'
import {AgeAssuranceAccess} from '#/ageAssurance/types' import {AgeAssuranceAccess} from '#/ageAssurance/types'
import { import {
ANDROID_API_LEVEL, ANDROID_API_LEVEL,
@@ -11,6 +6,7 @@ import {
IS_IOS, IS_IOS,
IS_WEB, IS_WEB,
} from '#/env' } from '#/env'
import {app} from '#/lexicons'
/** /**
* Minimum age required to access the app at all. * Minimum age required to access the app at all.
@@ -44,19 +40,17 @@ export const AGE_ASSURANCE_PLATFORM: 'web' | 'ios' | 'android' = IS_WEB
export const DEVICE_SIGNALS_SUPPORTED: boolean = export const DEVICE_SIGNALS_SUPPORTED: boolean =
(IS_IOS && IOS_MAJOR_VERSION >= 26) || (IS_ANDROID && ANDROID_API_LEVEL >= 23) (IS_IOS && IOS_MAJOR_VERSION >= 26) || (IS_ANDROID && ANDROID_API_LEVEL >= 23)
export const FALLBACK_REGION_CONFIG: AppBskyAgeassuranceDefs.ConfigRegion = { export const FALLBACK_REGION_CONFIG: app.bsky.ageassurance.defs.ConfigRegion = {
countryCode: '*', countryCode: '*',
regionCode: undefined, regionCode: undefined,
minAccessAge: MIN_ACCESS_AGE, minAccessAge: MIN_ACCESS_AGE,
rules: [ rules: [
{ app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({
$type: ids.IfDeclaredOverAge,
age: MIN_ACCESS_AGE, age: MIN_ACCESS_AGE,
access: AgeAssuranceAccess.Full, access: AgeAssuranceAccess.Full,
}, }),
{ app.bsky.ageassurance.defs.configRegionRuleDefault.build({
$type: ids.Default,
access: AgeAssuranceAccess.None, access: AgeAssuranceAccess.None,
}, }),
], ],
} }
+32 -28
View File
@@ -1,12 +1,6 @@
import {createContext, useCallback, useContext, useEffect, useMemo} from 'react' import {createContext, useCallback, useContext, useEffect, useMemo} from 'react'
import * as AgeRange from 'expo-age-range' import * as AgeRange from 'expo-age-range'
import { import {Client} from '@atproto/lex-client'
type AppBskyAgeassuranceDefs,
type AppBskyAgeassuranceGetConfig,
type AppBskyAgeassuranceGetState,
AtpAgent,
type ChatBskyActorDeclaration,
} from '@atproto/api'
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query' import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
import {persistQueryClient} from '@tanstack/react-query-persist-client' import {persistQueryClient} from '@tanstack/react-query-persist-client'
@@ -37,7 +31,9 @@ import {
} from '#/ageAssurance/util' } from '#/ageAssurance/util'
import {IS_DEV} from '#/env' import {IS_DEV} from '#/env'
import {useGeolocation} from '#/geolocation' import {useGeolocation} from '#/geolocation'
import {app, type chat} from '#/lexicons'
import {device} from '#/storage' import {device} from '#/storage'
import {toLex} from '#/types/bsky'
/** /**
* Special query client for age assurance data so we can prefetch on app * Special query client for age assurance data so we can prefetch on app
@@ -101,16 +97,15 @@ export function setBirthdateForDid({
export const configQueryKey = ['config'] export const configQueryKey = ['config']
export async function getConfig() { export async function getConfig() {
if (debug.enabled) return debug.resolve(debug.config) if (debug.enabled) return debug.resolve(debug.config)
const agent = new AtpAgent({ const client = new Client({
service: PUBLIC_BSKY_SERVICE, service: PUBLIC_BSKY_SERVICE,
}) })
const res = await agent.app.bsky.ageassurance.getConfig() return await client.call(app.bsky.ageassurance.getConfig)
return res.data
} }
export function getConfigFromCache(): export function getConfigFromCache():
| AppBskyAgeassuranceGetConfig.OutputSchema | app.bsky.ageassurance.getConfig.$OutputBody
| undefined { | undefined {
return qc.getQueryData<AppBskyAgeassuranceGetConfig.OutputSchema>( return qc.getQueryData<app.bsky.ageassurance.getConfig.$OutputBody>(
configQueryKey, configQueryKey,
) )
} }
@@ -131,7 +126,7 @@ export function prefetchConfig() {
try { try {
logger.debug(`prefetchAgeAssuranceConfig: resolving...`) logger.debug(`prefetchAgeAssuranceConfig: resolving...`)
const res = await networkRetry(3, () => getConfig()) const res = await networkRetry(3, () => getConfig())
qc.setQueryData<AppBskyAgeassuranceGetConfig.OutputSchema>( qc.setQueryData<app.bsky.ageassurance.getConfig.$OutputBody>(
configQueryKey, configQueryKey,
res, res,
) )
@@ -147,7 +142,7 @@ export function prefetchConfig() {
export async function refetchConfig() { export async function refetchConfig() {
logger.debug(`refetchConfig: fetching...`) logger.debug(`refetchConfig: fetching...`)
const res = await getConfig() const res = await getConfig()
qc.setQueryData<AppBskyAgeassuranceGetConfig.OutputSchema>( qc.setQueryData<app.bsky.ageassurance.getConfig.$OutputBody>(
configQueryKey, configQueryKey,
res, res,
) )
@@ -187,7 +182,11 @@ export function useConfigQuery() {
export function createServerStateQueryKey({did}: {did: string}) { export function createServerStateQueryKey({did}: {did: string}) {
return ['serverState', did] return ['serverState', did]
} }
export async function getServerState({agent}: {agent: SessionAgent}) { export async function getServerState({
agent,
}: {
agent: SessionAgent
}): Promise<app.bsky.ageassurance.getState.$OutputBody | null> {
if (debug.enabled && debug.serverState) if (debug.enabled && debug.serverState)
return debug.resolve(debug.serverState) return debug.resolve(debug.serverState)
const geolocation = device.get(['mergedGeolocation']) const geolocation = device.get(['mergedGeolocation'])
@@ -207,14 +206,19 @@ export async function getServerState({agent}: {agent: SessionAgent}) {
*/ */
data.metadata.accountCreatedAt = createdAtCache.get(did) data.metadata.accountCreatedAt = createdAtCache.get(did)
} }
return data ?? null /*
* TODO(phase4): the bridge agent still returns the old `@atproto/api`
* getState output; `toLex` reconciles it with the `#/lexicons` shape at this
* single boundary until the agent itself is migrated.
*/
return data ? toLex<app.bsky.ageassurance.getState.$OutputBody>(data) : null
} }
export function getServerStateFromCache({ export function getServerStateFromCache({
did, did,
}: { }: {
did: string did: string
}): AppBskyAgeassuranceGetState.OutputSchema | undefined { }): app.bsky.ageassurance.getState.$OutputBody | undefined {
return qc.getQueryData<AppBskyAgeassuranceGetState.OutputSchema>( return qc.getQueryData<app.bsky.ageassurance.getState.$OutputBody>(
createServerStateQueryKey({did}), createServerStateQueryKey({did}),
) )
} }
@@ -236,7 +240,7 @@ export async function prefetchServerState({agent}: {agent: SessionAgent}) {
logger.debug(`prefetchServerState: resolving...`) logger.debug(`prefetchServerState: resolving...`)
const res = await networkRetry(3, () => getServerState({agent})) const res = await networkRetry(3, () => getServerState({agent}))
if (res) { if (res) {
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(qk, res) qc.setQueryData<app.bsky.ageassurance.getState.$OutputBody>(qk, res)
} }
} catch (err) { } catch (err) {
const e = err as Error const e = err as Error
@@ -251,7 +255,7 @@ export async function refetchServerState({agent}: {agent: SessionAgent}) {
logger.debug(`refetchServerState: fetching...`) logger.debug(`refetchServerState: fetching...`)
const res = await networkRetry(3, () => getServerState({agent})) const res = await networkRetry(3, () => getServerState({agent}))
if (res) { if (res) {
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>( qc.setQueryData<app.bsky.ageassurance.getState.$OutputBody>(
createServerStateQueryKey({did}), createServerStateQueryKey({did}),
res, res,
) )
@@ -261,16 +265,16 @@ export async function refetchServerState({agent}: {agent: SessionAgent}) {
export function usePatchServerState() { export function usePatchServerState() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
return useCallback( return useCallback(
(next: AppBskyAgeassuranceDefs.State) => { (next: app.bsky.ageassurance.defs.State) => {
if (!currentAccount) return if (!currentAccount) return
const did = currentAccount.did const did = currentAccount.did
const prev = getServerStateFromCache({did}) const prev = getServerStateFromCache({did})
const merged: AppBskyAgeassuranceGetState.OutputSchema = { const merged: app.bsky.ageassurance.getState.$OutputBody = {
metadata: {}, metadata: {},
...(prev || {}), ...(prev || {}),
state: next, state: next,
} }
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>( qc.setQueryData<app.bsky.ageassurance.getState.$OutputBody>(
createServerStateQueryKey({did}), createServerStateQueryKey({did}),
merged, merged,
) )
@@ -336,7 +340,7 @@ export function useServerStateQuery() {
export type OtherRequiredData = { export type OtherRequiredData = {
birthdate: string | undefined birthdate: string | undefined
actorDeclaration?: ChatBskyActorDeclaration.Main actorDeclaration?: chat.bsky.actor.declaration.Main
} }
export function createOtherRequiredDataQueryKey({did}: {did: string}) { export function createOtherRequiredDataQueryKey({did}: {did: string}) {
return ['otherRequiredData', did] return ['otherRequiredData', did]
@@ -411,7 +415,7 @@ export function setOtherRequiredDataActorDeclarationCache({
actorDeclaration, actorDeclaration,
}: { }: {
did: string did: string
actorDeclaration: ChatBskyActorDeclaration.Main actorDeclaration: chat.bsky.actor.declaration.Main
}) { }) {
const prev = getOtherRequiredDataFromCache({did}) const prev = getOtherRequiredDataFromCache({did})
const next: OtherRequiredData = { const next: OtherRequiredData = {
@@ -551,7 +555,7 @@ export function getDeviceSignalsFromCacheForRegion({
region, region,
}: { }: {
did: string did: string
region: AppBskyAgeassuranceDefs.ConfigRegion region: app.bsky.ageassurance.defs.ConfigRegion
}): AgeRange.AgeRangeResponse | undefined { }): AgeRange.AgeRangeResponse | undefined {
const regionKey = createRegionKey(region) const regionKey = createRegionKey(region)
return getDeviceSignalsMapFromCache({did})?.[regionKey] return getDeviceSignalsMapFromCache({did})?.[regionKey]
@@ -695,11 +699,11 @@ export type AgeAssuranceServerData = {
/** /**
* The raw config from the appview. * The raw config from the appview.
*/ */
config: AppBskyAgeassuranceDefs.Config | undefined config: app.bsky.ageassurance.defs.Config | undefined
/** /**
* The raw state from the appview. Must be further processed before being useful. * The raw state from the appview. Must be further processed before being useful.
*/ */
state: AppBskyAgeassuranceDefs.State | undefined state: app.bsky.ageassurance.defs.State | undefined
metadata: AgeAssuranceMetadata | undefined metadata: AgeAssuranceMetadata | undefined
/** /**
* The native on-device age signals for the region the user is currently in, * The native on-device age signals for the region the user is currently in,
+50 -51
View File
@@ -1,13 +1,10 @@
import type * as AgeRange from 'expo-age-range' import type * as AgeRange from 'expo-age-range'
import { import {toDatetimeString} from '@atproto/syntax'
ageAssuranceRuleIDs as ids,
type AppBskyAgeassuranceDefs,
type AppBskyAgeassuranceGetState,
} from '@atproto/api'
import {type OtherRequiredData} from '#/ageAssurance/data' import {type OtherRequiredData} from '#/ageAssurance/data'
import {IS_DEV, IS_E2E} from '#/env' import {IS_DEV, IS_E2E} from '#/env'
import {type Geolocation} from '#/geolocation' import {type Geolocation} from '#/geolocation'
import {type app} from '#/lexicons'
export const enabled = (IS_DEV && false) || IS_E2E export const enabled = (IS_DEV && false) || IS_E2E
@@ -31,21 +28,22 @@ export const otherRequiredData: OtherRequiredData = {
} }
const serverStateEnabled = false || IS_E2E const serverStateEnabled = false || IS_E2E
export const serverState: AppBskyAgeassuranceGetState.OutputSchema | undefined = export const serverState:
serverStateEnabled | app.bsky.ageassurance.getState.$OutputBody
? { | undefined = serverStateEnabled
state: { ? {
lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(), state: {
status: 'unknown', lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(),
access: 'unknown', status: 'unknown',
}, access: 'unknown',
metadata: { },
accountCreatedAt: new Date(2023, 1, 1).toISOString(), metadata: {
}, accountCreatedAt: toDatetimeString(new Date(2023, 1, 1)),
} },
: undefined }
: undefined
export const config: AppBskyAgeassuranceDefs.Config = { export const config: app.bsky.ageassurance.defs.Config = {
regions: [ regions: [
{ {
countryCode: 'AA', countryCode: 'AA',
@@ -53,7 +51,7 @@ export const config: AppBskyAgeassuranceDefs.Config = {
minAccessAge: 13, minAccessAge: 13,
rules: [ rules: [
{ {
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
access: 'full', access: 'full',
}, },
], ],
@@ -71,11 +69,11 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -86,16 +84,16 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 13, age: 13,
access: 'safe', access: 'safe',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -106,26 +104,27 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
date: '2025-12-10T00:00:00Z', date: '2025-12-10T00:00:00Z',
access: 'none', access: 'none',
$type: ids.IfAccountNewerThan, $type:
'app.bsky.ageassurance.defs#configRegionRuleIfAccountNewerThan',
}, },
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 16, age: 16,
access: 'safe', access: 'safe',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 16, age: 16,
access: 'safe', access: 'safe',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -137,16 +136,16 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 13, age: 13,
access: 'safe', access: 'safe',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -158,16 +157,16 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 13, age: 13,
access: 'safe', access: 'safe',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -179,16 +178,16 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 13, age: 13,
access: 'safe', access: 'safe',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -200,11 +199,11 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -216,16 +215,16 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 16, age: 16,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 16, age: 16,
access: 'full', access: 'full',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -237,16 +236,16 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
@@ -257,21 +256,21 @@ export const config: AppBskyAgeassuranceDefs.Config = {
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfAssuredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfAssuredOverAge',
}, },
{ {
age: 18, age: 18,
access: 'full', access: 'full',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
age: 13, age: 13,
access: 'safe', access: 'safe',
$type: ids.IfDeclaredOverAge, $type: 'app.bsky.ageassurance.defs#configRegionRuleIfDeclaredOverAge',
}, },
{ {
access: 'none', access: 'none',
$type: ids.Default, $type: 'app.bsky.ageassurance.defs#configRegionRuleDefault',
}, },
], ],
}, },
+4 -6
View File
@@ -1,9 +1,6 @@
import {useEffect, useMemo, useState} from 'react' import {useEffect, useMemo, useState} from 'react'
import type * as AgeRange from 'expo-age-range' import type * as AgeRange from 'expo-age-range'
import { import {computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils'
type AppBskyAgeassuranceDefs,
computeAgeAssuranceRegionAccess,
} from '@atproto/api'
import {getAge} from '#/lib/strings/time' import {getAge} from '#/lib/strings/time'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
@@ -30,6 +27,7 @@ import {
getAgeAssuranceRegionConfigWithFallback, getAgeAssuranceRegionConfigWithFallback,
} from '#/ageAssurance/util' } from '#/ageAssurance/util'
import {type Geolocation, useGeolocation} from '#/geolocation' import {type Geolocation, useGeolocation} from '#/geolocation'
import {type app} from '#/lexicons'
import {device} from '#/storage' import {device} from '#/storage'
/** /**
@@ -47,8 +45,8 @@ function computeAgeAssuranceState({
}: { }: {
hasSession: boolean hasSession: boolean
geolocation: Geolocation geolocation: Geolocation
config?: AppBskyAgeassuranceDefs.Config config?: app.bsky.ageassurance.defs.Config
state?: AppBskyAgeassuranceDefs.State state?: app.bsky.ageassurance.defs.State
metadata?: AgeAssuranceMetadata metadata?: AgeAssuranceMetadata
deviceSignals?: AgeRange.AgeRangeResponse deviceSignals?: AgeRange.AgeRangeResponse
}) { }) {
+1 -1
View File
@@ -1,5 +1,5 @@
import type * as AgeRange from 'expo-age-range' import type * as AgeRange from 'expo-age-range'
import {type computeAgeAssuranceRegionAccess} from '@atproto/api' import {type computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils'
import {logger} from '#/ageAssurance/logger' import {logger} from '#/ageAssurance/logger'
@@ -1,5 +1,5 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {computeAgeAssuranceRegionAccess} from '@atproto/api' import {computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils'
import {useAgeAssuranceServerDataContext} from '#/ageAssurance/data' import {useAgeAssuranceServerDataContext} from '#/ageAssurance/data'
import {logger} from '#/ageAssurance/logger' import {logger} from '#/ageAssurance/logger'
+19 -15
View File
@@ -1,11 +1,10 @@
import {useMemo} from 'react' import {useMemo} from 'react'
import type * as AgeRange from 'expo-age-range' import type * as AgeRange from 'expo-age-range'
import {type ModerationPrefs} from '@bsky.app/sdk/moderation'
import { import {
AppBskyAgeassuranceDefs,
computeAgeAssuranceRegionAccess, computeAgeAssuranceRegionAccess,
getAgeAssuranceRegionConfig, getAgeAssuranceRegionConfig,
type ModerationPrefs, } from '@bsky.app/sdk/utils'
} from '@atproto/api'
import {getAge} from '#/lib/strings/time' import {getAge} from '#/lib/strings/time'
import {regionName} from '#/locale/helpers' import {regionName} from '#/locale/helpers'
@@ -25,6 +24,7 @@ import {
} from '#/ageAssurance/types' } from '#/ageAssurance/types'
import {type Geolocation, useGeolocation} from '#/geolocation' import {type Geolocation, useGeolocation} from '#/geolocation'
import {USRegionNameToRegionCode} from '#/geolocation/util' import {USRegionNameToRegionCode} from '#/geolocation/util'
import {app} from '#/lexicons'
/** /**
* Resolves a geolocation to its matched age assurance region config, or * Resolves a geolocation to its matched age assurance region config, or
@@ -41,9 +41,9 @@ import {USRegionNameToRegionCode} from '#/geolocation/util'
* risk desyncing the write and read keys and silently losing grants. * risk desyncing the write and read keys and silently losing grants.
*/ */
export function getAgeAssuranceRegionConfigForGeolocation( export function getAgeAssuranceRegionConfigForGeolocation(
config: AppBskyAgeassuranceDefs.Config, config: app.bsky.ageassurance.defs.Config,
geolocation: Geolocation, geolocation: Geolocation,
): AppBskyAgeassuranceDefs.ConfigRegion | undefined { ): app.bsky.ageassurance.defs.ConfigRegion | undefined {
return getAgeAssuranceRegionConfig(config, { return getAgeAssuranceRegionConfig(config, {
countryCode: geolocation.countryCode ?? '', countryCode: geolocation.countryCode ?? '',
regionCode: geolocation.regionCode, regionCode: geolocation.regionCode,
@@ -59,9 +59,9 @@ export function getAgeAssuranceRegionConfigForGeolocation(
* which can return undefined if the geolocation does not match any AA region. * which can return undefined if the geolocation does not match any AA region.
*/ */
export function getAgeAssuranceRegionConfigWithFallback( export function getAgeAssuranceRegionConfigWithFallback(
config: AppBskyAgeassuranceDefs.Config, config: app.bsky.ageassurance.defs.Config,
geolocation: Geolocation, geolocation: Geolocation,
): AppBskyAgeassuranceDefs.ConfigRegion { ): app.bsky.ageassurance.defs.ConfigRegion {
return ( return (
getAgeAssuranceRegionConfigForGeolocation(config, geolocation) || getAgeAssuranceRegionConfigForGeolocation(config, geolocation) ||
FALLBACK_REGION_CONFIG FALLBACK_REGION_CONFIG
@@ -74,9 +74,9 @@ export function getAgeAssuranceRegionConfigWithFallback(
* historical KWS-only behavior). * historical KWS-only behavior).
*/ */
export function getRegionAdditionalVerificationMethods( export function getRegionAdditionalVerificationMethods(
region: AppBskyAgeassuranceDefs.ConfigRegion, region: app.bsky.ageassurance.defs.ConfigRegion,
): NonNullable< ): NonNullable<
AppBskyAgeassuranceDefs.ConfigRegion['additionalVerificationMethods'] app.bsky.ageassurance.defs.ConfigRegion['additionalVerificationMethods']
> { > {
return region.additionalVerificationMethods ?? [] return region.additionalVerificationMethods ?? []
} }
@@ -86,7 +86,7 @@ export function getRegionAdditionalVerificationMethods(
* age APIs (Apple Declared Age Range / Google Play Age Signals). * age APIs (Apple Declared Age Range / Google Play Age Signals).
*/ */
export function regionAllowsDeviceVerification( export function regionAllowsDeviceVerification(
region: AppBskyAgeassuranceDefs.ConfigRegion, region: app.bsky.ageassurance.defs.ConfigRegion,
): boolean { ): boolean {
return getRegionAdditionalVerificationMethods(region).includes('device') return getRegionAdditionalVerificationMethods(region).includes('device')
} }
@@ -122,7 +122,7 @@ export function createRegionKey(region: {
* usable data. * usable data.
*/ */
export function getAgeAssuranceDataFromDeviceSignals( export function getAgeAssuranceDataFromDeviceSignals(
region: AppBskyAgeassuranceDefs.ConfigRegion, region: app.bsky.ageassurance.defs.ConfigRegion,
deviceSignals: AgeRange.AgeRangeResponse | undefined, deviceSignals: AgeRange.AgeRangeResponse | undefined,
): { ): {
assuredAge?: number assuredAge?: number
@@ -183,7 +183,7 @@ export function canBirthdateUpdateIncreaseAccess({
metadata, metadata,
deviceSignals, deviceSignals,
}: { }: {
region: AppBskyAgeassuranceDefs.ConfigRegion region: app.bsky.ageassurance.defs.ConfigRegion
metadata?: AgeAssuranceMetadata metadata?: AgeAssuranceMetadata
deviceSignals?: AgeRange.AgeRangeResponse deviceSignals?: AgeRange.AgeRangeResponse
}): boolean { }): boolean {
@@ -212,8 +212,12 @@ export function canBirthdateUpdateIncreaseAccess({
const thresholds = new Set<number>([region.minAccessAge]) const thresholds = new Set<number>([region.minAccessAge])
for (const rule of region.rules) { for (const rule of region.rules) {
if ( if (
AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredOverAge(rule) || app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.$isTypeOf(
AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredUnderAge(rule) rule,
) ||
app.bsky.ageassurance.defs.configRegionRuleIfDeclaredUnderAge.$isTypeOf(
rule,
)
) { ) {
thresholds.add(rule.age) thresholds.add(rule.age)
} }
@@ -303,7 +307,7 @@ export function computeAgeAssuranceFlags({
deviceSignals, deviceSignals,
}: { }: {
state: AgeAssuranceState state: AgeAssuranceState
regionConfig: AppBskyAgeassuranceDefs.ConfigRegion regionConfig: app.bsky.ageassurance.defs.ConfigRegion
metadata?: AgeAssuranceMetadata metadata?: AgeAssuranceMetadata
deviceSignals?: AgeRange.AgeRangeResponse deviceSignals?: AgeRange.AgeRangeResponse
}): AgeAssuranceFlags { }): AgeAssuranceFlags {
+2 -2
View File
@@ -1,6 +1,5 @@
import {Fragment, useCallback} from 'react' import {Fragment, useCallback} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -19,6 +18,7 @@ import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/
import {ProfileBadges} from '#/components/ProfileBadges' import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useActorStatus} from '#/features/liveNow' import {useActorStatus} from '#/features/liveNow'
import {type app} from '#/lexicons'
export function AccountList({ export function AccountList({
onSelectAccount, onSelectAccount,
@@ -107,7 +107,7 @@ function AccountItem({
isCurrentAccount, isCurrentAccount,
isPendingAccount, isPendingAccount,
}: { }: {
profile?: AppBskyActorDefs.ProfileViewDetailed profile?: app.bsky.actor.defs.ProfileViewDetailed
account: SessionAccount account: SessionAccount
onSelect: (account: SessionAccount) => void onSelect: (account: SessionAccount) => void
isCurrentAccount: boolean isCurrentAccount: boolean
@@ -62,7 +62,9 @@ export function useAutocomplete({
key: profile.did, key: profile.did,
type: 'profile' as const, type: 'profile' as const,
value: '@' + profile.handle, value: '@' + profile.handle,
profile, // TODO(phase4): drop toLex once searchActorsTypeahead (bridge agent)
// emits #/lexicons views
profile: toLex<AutocompleteProfile['profile']>(profile),
})) }))
} else if (type === 'emoji') { } else if (type === 'emoji') {
return emojiSearch(q, limit || 8) return emojiSearch(q, limit || 8)
+2 -2
View File
@@ -1,5 +1,4 @@
import {type Insets, View} from 'react-native' import {type Insets, View} from 'react-native'
import {type ComAtprotoLabelDefs} from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
@@ -8,11 +7,12 @@ import {Button} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog'
import {Bot_Filled as RobotIcon} from '#/components/icons/Bot' import {Bot_Filled as RobotIcon} from '#/components/icons/Bot'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type com} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function isBotAccount(profile: { export function isBotAccount(profile: {
did: string did: string
labels?: ComAtprotoLabelDefs.Label[] labels?: com.atproto.label.defs.Label[]
}): boolean { }): boolean {
return ( return (
profile.labels?.some(l => l.val === 'bot' && l.src === profile.did) ?? false profile.labels?.some(l => l.val === 'bot' && l.src === profile.did) ?? false
+5 -5
View File
@@ -1,6 +1,5 @@
import {useCallback, useEffect, useMemo} from 'react' import {useCallback, useEffect, useMemo} from 'react'
import {type GestureResponderEvent, View} from 'react-native' import {type GestureResponderEvent, View} from 'react-native'
import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {RichText as RichTextApi} from '@bsky.app/sdk/richtext'
import {Plural, Trans, useLingui} from '@lingui/react/macro' import {Plural, Trans, useLingui} from '@lingui/react/macro'
@@ -32,11 +31,12 @@ import {RichText, type RichTextProps} from '#/components/RichText'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useActiveLiveEventFeedUris} from '#/features/liveEvents/context' import {useActiveLiveEventFeedUris} from '#/features/liveEvents/context'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from './icons/Trash' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from './icons/Trash'
type Props = { type Props = {
view: AppBskyFeedDefs.GeneratorView view: app.bsky.feed.defs.GeneratorView
onPress?: () => void onPress?: () => void
} }
@@ -255,7 +255,7 @@ export function SaveButton({
pin, pin,
...props ...props
}: { }: {
view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView view: app.bsky.feed.defs.GeneratorView | app.bsky.graph.defs.ListView
pin?: boolean pin?: boolean
text?: boolean text?: boolean
} & Partial<ButtonProps>) { } & Partial<ButtonProps>) {
@@ -270,7 +270,7 @@ function SaveButtonInner({
text = true, text = true,
...buttonProps ...buttonProps
}: { }: {
view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView view: app.bsky.feed.defs.GeneratorView | app.bsky.graph.defs.ListView
pin?: boolean pin?: boolean
text?: boolean text?: boolean
} & Partial<ButtonProps>) { } & Partial<ButtonProps>) {
@@ -380,7 +380,7 @@ function SaveButtonInner({
export function createProfileFeedHref({ export function createProfileFeedHref({
feed, feed,
}: { }: {
feed: AppBskyFeedDefs.GeneratorView feed: app.bsky.feed.defs.GeneratorView
}) { }) {
const urip = new AtUri(feed.uri) const urip = new AtUri(feed.uri)
const handleOrDid = feed.creator.handle || feed.creator.did const handleOrDid = feed.creator.handle || feed.creator.did
+2 -2
View File
@@ -7,7 +7,6 @@ import Animated, {
LayoutAnimationConfig, LayoutAnimationConfig,
LinearTransition, LinearTransition,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {type AppBskyFeedDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
@@ -40,6 +39,7 @@ import {ProgressGuideList} from '#/components/ProgressGuide/List'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type Metrics, useAnalytics} from '#/analytics' import {type Metrics, useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog' import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog'
@@ -569,7 +569,7 @@ export function SuggestedFeeds() {
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const feeds = useMemo(() => { const feeds = useMemo(() => {
const items: AppBskyFeedDefs.GeneratorView[] = [] const items: app.bsky.feed.defs.GeneratorView[] = []
if (!data) return items if (!data) return items
+6 -4
View File
@@ -1,6 +1,5 @@
import {useRef} from 'react' import {useRef} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
import {Plural, Trans, useLingui} from '@lingui/react/macro' import {Plural, Trans, useLingui} from '@lingui/react/macro'
@@ -10,6 +9,7 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Link, type LinkProps} from '#/components/Link' import {Link, type LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky' import {toLex} from '#/types/bsky'
@@ -24,7 +24,7 @@ const AVI_BORDER = 1
* `count` includes blocked users and `followers` does not. * `count` includes blocked users and `followers` does not.
*/ */
export function shouldShowKnownFollowers( export function shouldShowKnownFollowers(
knownFollowers?: AppBskyActorDefs.KnownFollowers, knownFollowers?: app.bsky.actor.defs.KnownFollowers,
) { ) {
return knownFollowers && knownFollowers.followers.length > 0 return knownFollowers && knownFollowers.followers.length > 0
} }
@@ -42,7 +42,9 @@ export function KnownFollowers({
minimal?: boolean minimal?: boolean
showIfEmpty?: boolean showIfEmpty?: boolean
}) { }) {
const cache = useRef<Map<string, AppBskyActorDefs.KnownFollowers>>(new Map()) const cache = useRef<Map<string, app.bsky.actor.defs.KnownFollowers>>(
new Map(),
)
/* /*
* Results for `knownFollowers` are not sorted consistently, so when * Results for `knownFollowers` are not sorted consistently, so when
@@ -83,7 +85,7 @@ function KnownFollowersInner({
}: { }: {
profile: bsky.profile.AnyProfileView profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
cachedKnownFollowers: AppBskyActorDefs.KnownFollowers cachedKnownFollowers: app.bsky.actor.defs.KnownFollowers
onLinkPress?: LinkProps['onPress'] onLinkPress?: LinkProps['onPress']
minimal?: boolean minimal?: boolean
showIfEmpty?: boolean showIfEmpty?: boolean
+3 -3
View File
@@ -1,5 +1,4 @@
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyLabelerDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro' import {Plural, Trans} from '@lingui/react/macro'
@@ -13,10 +12,11 @@ import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
import {Link as InternalLink, type LinkProps} from '#/components/Link' import {Link as InternalLink, type LinkProps} from '#/components/Link'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type app} from '#/lexicons'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
type LabelingServiceProps = { type LabelingServiceProps = {
labeler: AppBskyLabelerDefs.LabelerViewDetailed labeler: app.bsky.labeler.defs.LabelerViewDetailed
} }
export function Outer({ export function Outer({
@@ -187,7 +187,7 @@ export function Loader({
loading?: React.ComponentType<{}> loading?: React.ComponentType<{}>
error?: React.ComponentType<{error: string}> error?: React.ComponentType<{error: string}>
component: React.ComponentType<{ component: React.ComponentType<{
labeler: AppBskyLabelerDefs.LabelerViewDetailed labeler: app.bsky.labeler.defs.LabelerViewDetailed
}> }>
}) { }) {
const {isLoading, data, error} = useLabelerInfoQuery({did}) const {isLoading, data, error} = useLabelerInfoQuery({did})
+9 -3
View File
@@ -1,5 +1,4 @@
import {useCallback, useMemo, useState} from 'react' import {useCallback, useMemo, useState} from 'react'
import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -11,8 +10,15 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List' import {List} from '#/view/com/util/List'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {type app} from '#/lexicons'
function renderItem({item, index}: {item: GetLikes.Like; index: number}) { function renderItem({
item,
index,
}: {
item: app.bsky.feed.getLikes.Like
index: number
}) {
return ( return (
<ProfileCardWithFollowBtn <ProfileCardWithFollowBtn
key={item.actor.did} key={item.actor.did}
@@ -22,7 +28,7 @@ function renderItem({item, index}: {item: GetLikes.Like; index: number}) {
) )
} }
function keyExtractor(item: GetLikes.Like) { function keyExtractor(item: app.bsky.feed.getLikes.Like) {
return item.actor.did return item.actor.did
} }
+4 -4
View File
@@ -1,6 +1,5 @@
import {useEffect, useMemo} from 'react' import {useEffect, useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyGraphDefs} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {moderateUserList, type ModerationUI} from '@bsky.app/sdk/moderation' import {moderateUserList, type ModerationUI} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
@@ -23,6 +22,7 @@ import {
import {Link as InternalLink, type LinkProps} from '#/components/Link' import {Link as InternalLink, type LinkProps} from '#/components/Link'
import * as Hider from '#/components/moderation/Hider' import * as Hider from '#/components/moderation/Hider'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky' import {toLex} from '#/types/bsky'
@@ -45,7 +45,7 @@ const CURATELIST = 'app.bsky.graph.defs#curatelist'
const MODLIST = 'app.bsky.graph.defs#modlist' const MODLIST = 'app.bsky.graph.defs#modlist'
type Props = { type Props = {
view: AppBskyGraphDefs.ListView view: app.bsky.graph.defs.ListView
showPinButton?: boolean showPinButton?: boolean
} }
@@ -110,7 +110,7 @@ export function TitleAndByline({
}: { }: {
title: string title: string
creator?: bsky.profile.AnyProfileView creator?: bsky.profile.AnyProfileView
purpose?: AppBskyGraphDefs.ListView['purpose'] purpose?: app.bsky.graph.defs.ListView['purpose']
modUi?: ModerationUI modUi?: ModerationUI
}) { }) {
const t = useTheme() const t = useTheme()
@@ -159,7 +159,7 @@ export function TitleAndByline({
export function createProfileListHref({ export function createProfileListHref({
list, list,
}: { }: {
list: AppBskyGraphDefs.ListView list: app.bsky.graph.defs.ListView
}) { }) {
const urip = new AtUri(list.uri) const urip = new AtUri(list.uri)
const handleOrDid = list.creator.handle || list.creator.did const handleOrDid = list.creator.handle || list.creator.did
+11 -10
View File
@@ -1,10 +1,5 @@
import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {
AppBskyEmbedGallery,
type AppBskyEmbedImages,
type AppBskyFeedDefs,
} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {shareImageModal} from '#/lib/media/manip' import {shareImageModal} from '#/lib/media/manip'
@@ -17,6 +12,7 @@ import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import * as PeekMenu from '#/components/PeekMenu' import * as PeekMenu from '#/components/PeekMenu'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
/** /**
@@ -27,7 +23,7 @@ export function Embed({
style, style,
peekable = false, peekable = false,
}: { }: {
embed: AppBskyFeedDefs.PostView['embed'] embed: app.bsky.feed.defs.PostView['embed']
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
peekable?: boolean peekable?: boolean
}) { }) {
@@ -59,9 +55,9 @@ export function Embed({
const tiles: React.ReactNode[] = [] const tiles: React.ReactNode[] = []
for (const item of e.view.items) { for (const item of e.view.items) {
if (tiles.length >= 4) break if (tiles.length >= 4) break
if (!AppBskyEmbedGallery.isViewImage(item)) continue if (!bsky.isType(app.bsky.embed.gallery.viewImage, item)) continue
if (peekable) { if (peekable) {
const image: AppBskyEmbedImages.ViewImage = { const image: app.bsky.embed.images.ViewImage = {
thumb: item.thumbnail, thumb: item.thumbnail,
fullsize: item.fullsize, fullsize: item.fullsize,
alt: item.alt, alt: item.alt,
@@ -107,7 +103,12 @@ export function Embed({
// ignore any unknowns // ignore any unknowns
e.media.view !== null e.media.view !== null
) { ) {
return <Embed embed={e.media.view} style={style} /> return (
<Embed
embed={e.media.view as app.bsky.feed.defs.PostView['embed']}
style={style}
/>
)
} }
return null return null
@@ -202,7 +203,7 @@ export function VideoItem({
) )
} }
function PeekableImageItem({image}: {image: AppBskyEmbedImages.ViewImage}) { function PeekableImageItem({image}: {image: app.bsky.embed.images.ViewImage}) {
const {t: l} = useLingui() const {t: l} = useLingui()
const saveImage = useSaveImageToMediaLibrary() const saveImage = useSaveImageToMediaLibrary()
+3 -3
View File
@@ -1,6 +1,5 @@
import {useMemo, useState} from 'react' import {useMemo, useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {moderateProfile} from '@bsky.app/sdk/moderation' import {moderateProfile} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -20,13 +19,14 @@ import {Newskie} from '#/components/icons/Newskie'
import * as StarterPackCard from '#/components/StarterPack/StarterPackCard' import * as StarterPackCard from '#/components/StarterPack/StarterPackCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky' import {toLex} from '#/types/bsky'
export function NewskieDialog({ export function NewskieDialog({
profile, profile,
disabled, disabled,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed profile: app.bsky.actor.defs.ProfileViewDetailed
disabled?: boolean disabled?: boolean
}) { }) {
const t = useTheme() const t = useTheme()
@@ -76,7 +76,7 @@ function DialogInner({
createdAt, createdAt,
now, now,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed profile: app.bsky.actor.defs.ProfileViewDetailed
createdAt: string createdAt: string
now: number now: number
}) { }) {
@@ -1,10 +1,10 @@
import {type StyleProp, type ViewStyle} from 'react-native' import {type StyleProp, type ViewStyle} from 'react-native'
import {type AppBskyEmbedExternal} from '@atproto/api'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import * as ChatInvite from '#/components/dms/ChatInvite' import * as ChatInvite from '#/components/dms/ChatInvite'
import {ExternalEmbed} from '#/components/Post/Embed/ExternalEmbed' import {ExternalEmbed} from '#/components/Post/Embed/ExternalEmbed'
import {JoinRequestEmbedBody} from '#/components/Post/Embed/JoinRequestEmbed' import {JoinRequestEmbedBody} from '#/components/Post/Embed/JoinRequestEmbed'
import {type app} from '#/lexicons'
/** /**
* Renders a chat invite link found in an `app.bsky.embed.external` embed (e.g. * Renders a chat invite link found in an `app.bsky.embed.external` embed (e.g.
@@ -18,7 +18,7 @@ export function ChatInviteEmbed({
style, style,
}: { }: {
code: string code: string
link: AppBskyEmbedExternal.ViewExternal link: app.bsky.embed.external.ViewExternal
onOpen?: () => void onOpen?: () => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
@@ -34,7 +34,7 @@ function ChatInviteEmbedBody({
onOpen, onOpen,
style, style,
}: { }: {
link: AppBskyEmbedExternal.ViewExternal link: app.bsky.embed.external.ViewExternal
onOpen?: () => void onOpen?: () => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
@@ -5,7 +5,6 @@ import {
Pressable, Pressable,
} from 'react-native' } from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {type AppBskyEmbedExternal} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -17,12 +16,13 @@ import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
import {Fill} from '#/components/Fill' import {Fill} from '#/components/Fill'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
import {type app} from '#/lexicons'
export function ExternalGif({ export function ExternalGif({
link, link,
params, params,
}: { }: {
link: AppBskyEmbedExternal.ViewExternal link: app.bsky.embed.external.ViewExternal
params: EmbedPlayerParams params: EmbedPlayerParams
}) { }) {
const t = useTheme() const t = useTheme()
@@ -15,7 +15,6 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {WebView} from 'react-native-webview' import {WebView} from 'react-native-webview'
import {scheduleOnRN} from 'react-native-worklets' import {scheduleOnRN} from 'react-native-worklets'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {type AppBskyEmbedExternal} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
@@ -34,6 +33,7 @@ import {Fill} from '#/components/Fill'
import {KeepAwake} from '#/components/KeepAwake' import {KeepAwake} from '#/components/KeepAwake'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
interface ShouldStartLoadRequest { interface ShouldStartLoadRequest {
url: string url: string
@@ -122,7 +122,7 @@ export function ExternalPlayer({
link, link,
params, params,
}: { }: {
link: AppBskyEmbedExternal.ViewExternal link: app.bsky.embed.external.ViewExternal
params: EmbedPlayerParams params: EmbedPlayerParams
}) { }) {
const t = useTheme() const t = useTheme()
@@ -1,7 +1,6 @@
import {useMemo} from 'react' import {useMemo} from 'react'
import {type StyleProp, View, type ViewStyle} from 'react-native' import {type StyleProp, View, type ViewStyle} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {type AppBskyEmbedExternal} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -20,6 +19,7 @@ import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
import {ExternalGif} from './ExternalGif' import {ExternalGif} from './ExternalGif'
import {ExternalPlayer} from './ExternalPlayer' import {ExternalPlayer} from './ExternalPlayer'
import {GifEmbed} from './Gif' import {GifEmbed} from './Gif'
@@ -30,7 +30,7 @@ export const ExternalEmbed = ({
style, style,
hideAlt, hideAlt,
}: { }: {
link: AppBskyEmbedExternal.ViewExternal link: app.bsky.embed.external.ViewExternal
onOpen?: () => void onOpen?: () => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
hideAlt?: boolean hideAlt?: boolean
+11 -8
View File
@@ -2,7 +2,6 @@ import {useRef} from 'react'
import {InteractionManager, View} from 'react-native' import {InteractionManager, View} from 'react-native'
import {type AnimatedRef} from 'react-native-reanimated' import {type AnimatedRef} from 'react-native-reanimated'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {AppBskyEmbedGallery, type AppBskyEmbedImages} from '@atproto/api'
import {atoms as a, tokens} from '#/alf' import {atoms as a, tokens} from '#/alf'
import {AutoSizedImage} from '#/components/images/AutoSizedImage' import {AutoSizedImage} from '#/components/images/AutoSizedImage'
@@ -16,6 +15,8 @@ import {type Dimensions} from '#/components/Lightbox/types'
import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu'
import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {PostEmbedViewContext} from '#/components/Post/Embed/types'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {type EmbedType} from '#/types/bsky/post' import {type EmbedType} from '#/types/bsky/post'
import {type CommonProps} from './types' import {type CommonProps} from './types'
@@ -29,14 +30,16 @@ export function ImageEmbed({
}) { }) {
const ax = useAnalytics() const ax = useAnalytics()
const {openLightbox} = useLightboxControls() const {openLightbox} = useLightboxControls()
const images: AppBskyEmbedImages.ViewImage[] = const images: app.bsky.embed.images.ViewImage[] =
embed.type === 'gallery' embed.type === 'gallery'
? embed.view.items.filter(AppBskyEmbedGallery.isViewImage).map(item => ({ ? embed.view.items
thumb: item.thumbnail, .filter(item => bsky.isType(app.bsky.embed.gallery.viewImage, item))
fullsize: item.fullsize, .map(item => ({
alt: item.alt, thumb: item.thumbnail,
aspectRatio: item.aspectRatio, fullsize: item.fullsize,
})) alt: item.alt,
aspectRatio: item.aspectRatio,
}))
: embed.view.images : embed.view.images
const useExpandedLayout = const useExpandedLayout =
embed.type === 'gallery' embed.type === 'gallery'
@@ -1,6 +1,5 @@
import {type AppBskyEmbedExternal} from '@atproto/api'
import {Context, useAlf, utils} from '#/alf' import {Context, useAlf, utils} from '#/alf'
import {type app} from '#/lexicons'
/** /**
* Overrides only the values needed for `secondary_inverted` buttons atm. * Overrides only the values needed for `secondary_inverted` buttons atm.
@@ -12,7 +11,7 @@ export function StandardSiteThemeProvider({
view, view,
children, children,
}: { }: {
view: AppBskyEmbedExternal.ViewExternal view: app.bsky.embed.external.ViewExternal
children: React.ReactNode children: React.ReactNode
}) { }) {
const alf = useAlf() const alf = useAlf()
@@ -1,8 +1,7 @@
import {type AppBskyEmbedExternal} from '@atproto/api'
import {Leaflet} from '#/components/icons/community/Leaflet' import {Leaflet} from '#/components/icons/community/Leaflet'
import {Offprint} from '#/components/icons/community/Offprint' import {Offprint} from '#/components/icons/community/Offprint'
import {Pckt} from '#/components/icons/community/Pckt' import {Pckt} from '#/components/icons/community/Pckt'
import {type app} from '#/lexicons'
export type StandardSitePublisher = { export type StandardSitePublisher = {
host: string host: string
@@ -25,7 +24,7 @@ function hostFromUri(uri: string | undefined): string | null {
} }
export function getStandardSitePublisherHost( export function getStandardSitePublisherHost(
view: AppBskyEmbedExternal.ViewExternal, view: app.bsky.embed.external.ViewExternal,
): string | null { ): string | null {
return hostFromUri(view.source?.uri) return hostFromUri(view.source?.uri)
} }
@@ -40,7 +39,7 @@ function matchByHost(host: string | null): StandardSitePublisher | null {
} }
export function matchStandardSitePublisher( export function matchStandardSitePublisher(
view: AppBskyEmbedExternal.ViewExternal, view: app.bsky.embed.external.ViewExternal,
): StandardSitePublisher | null { ): StandardSitePublisher | null {
return matchByHost(getStandardSitePublisherHost(view)) return matchByHost(getStandardSitePublisherHost(view))
} }
@@ -1,7 +1,7 @@
import {type AppBskyEmbedExternal} from '@atproto/api' import {type app} from '#/lexicons'
export type CommonProps = { export type CommonProps = {
view: AppBskyEmbedExternal.ViewExternal view: app.bsky.embed.external.ViewExternal
} }
export type PreviewProps = { export type PreviewProps = {
@@ -1,10 +1,9 @@
import {type AppBskyEmbedExternal} from '@atproto/api' import {type app} from '#/lexicons'
import {isStandardSiteEmbed, isStandardSitePublicationEmbed} from './utils' import {isStandardSiteEmbed, isStandardSitePublicationEmbed} from './utils'
function makeView( function makeView(
partial: Record<string, unknown>, partial: Record<string, unknown>,
): AppBskyEmbedExternal.ViewExternal { ): app.bsky.embed.external.ViewExternal {
return { return {
uri: 'https://example.com/post', uri: 'https://example.com/post',
title: 'title', title: 'title',
@@ -1,29 +1,31 @@
import {
type AppBskyEmbedExternal,
type ComAtprotoRepoStrongRef,
} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
export function isStandardSiteDocumentUri(ref: ComAtprotoRepoStrongRef.Main) { import {type app, type com} from '#/lexicons'
export function isStandardSiteDocumentUri(
ref: com.atproto.repo.strongRef.Main,
) {
return new AtUri(ref.uri).collection.startsWith('site.standard.document') return new AtUri(ref.uri).collection.startsWith('site.standard.document')
} }
export function isStandardSitePublicationUri( export function isStandardSitePublicationUri(
ref: ComAtprotoRepoStrongRef.Main, ref: com.atproto.repo.strongRef.Main,
) { ) {
return new AtUri(ref.uri).collection.startsWith('site.standard.publication') return new AtUri(ref.uri).collection.startsWith('site.standard.publication')
} }
export function isStandardSiteUri(ref: ComAtprotoRepoStrongRef.Main) { export function isStandardSiteUri(ref: com.atproto.repo.strongRef.Main) {
return new AtUri(ref.uri).collection.startsWith('site.standard.') return new AtUri(ref.uri).collection.startsWith('site.standard.')
} }
export function isStandardSiteEmbed(view: AppBskyEmbedExternal.ViewExternal) { export function isStandardSiteEmbed(
view: app.bsky.embed.external.ViewExternal,
) {
return view.associatedRefs?.some(ref => isStandardSiteUri(ref)) return view.associatedRefs?.some(ref => isStandardSiteUri(ref))
} }
export function isStandardSitePublicationEmbed( export function isStandardSitePublicationEmbed(
view: AppBskyEmbedExternal.ViewExternal, view: app.bsky.embed.external.ViewExternal,
) { ) {
return ( return (
view.associatedRefs?.some( view.associatedRefs?.some(
@@ -1,6 +1,5 @@
import {useImperativeHandle, useRef, useState} from 'react' import {useImperativeHandle, useRef, useState} from 'react'
import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native'
import {type AppBskyEmbedVideo} from '@atproto/api'
import {BlueskyVideoView} from '@bsky.app/video' import {BlueskyVideoView} from '@bsky.app/video'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -17,6 +16,7 @@ import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/compone
import {KeepAwake} from '#/components/KeepAwake' import {KeepAwake} from '#/components/KeepAwake'
import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {useVideoMuteState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' import {useVideoMuteState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
import {type app} from '#/lexicons'
import {GifPresentationControls} from '../GifPresentationControls' import {GifPresentationControls} from '../GifPresentationControls'
import {TimeIndicator} from './TimeIndicator' import {TimeIndicator} from './TimeIndicator'
@@ -29,7 +29,7 @@ export function VideoEmbedInnerNative({
onError, onError,
}: { }: {
ref: React.Ref<{togglePlayback: () => void}> ref: React.Ref<{togglePlayback: () => void}>
embed: AppBskyEmbedVideo.View embed: app.bsky.embed.video.View
setStatus: (status: 'playing' | 'paused') => void setStatus: (status: 'playing' | 'paused') => void
setIsLoading: (isLoading: boolean) => void setIsLoading: (isLoading: boolean) => void
setIsActive: (isActive: boolean) => void setIsActive: (isActive: boolean) => void
@@ -1,7 +1,7 @@
import {type AppBskyEmbedVideo} from '@atproto/api' import {type app} from '#/lexicons'
export type VideoEmbedInnerWebProps = { export type VideoEmbedInnerWebProps = {
embed: AppBskyEmbedVideo.View embed: app.bsky.embed.video.View
active: boolean active: boolean
setActive: () => void setActive: () => void
onScreen: boolean onScreen: boolean
@@ -1,7 +1,6 @@
import {useCallback, useEffect, useRef, useState} from 'react' import {useCallback, useEffect, useRef, useState} from 'react'
import {ActivityIndicator, View} from 'react-native' import {ActivityIndicator, View} from 'react-native'
import {ImageBackground} from 'expo-image' import {ImageBackground} from 'expo-image'
import {type AppBskyEmbedVideo} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -17,12 +16,13 @@ import {useThrottledValue} from '#/components/hooks/useThrottledValue'
import {ConstrainedImage} from '#/components/images/AutoSizedImage' import {ConstrainedImage} from '#/components/images/AutoSizedImage'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import {GifPresentationControls} from './GifPresentationControls' import {GifPresentationControls} from './GifPresentationControls'
import {VideoEmbedInnerNative} from './VideoEmbedInner/VideoEmbedInnerNative' import {VideoEmbedInnerNative} from './VideoEmbedInner/VideoEmbedInnerNative'
import * as VideoFallback from './VideoEmbedInner/VideoFallback' import * as VideoFallback from './VideoEmbedInner/VideoFallback'
interface Props { interface Props {
embed: AppBskyEmbedVideo.View embed: app.bsky.embed.video.View
} }
export function VideoEmbed({embed}: Props) { export function VideoEmbed({embed}: Props) {
@@ -7,7 +7,6 @@ import {
useState, useState,
} from 'react' } from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyEmbedVideo} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -25,6 +24,7 @@ import {
} from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb' } from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_WEB_FIREFOX} from '#/env' import {IS_WEB_FIREFOX} from '#/env'
import {type app} from '#/lexicons'
import {useActiveVideoWeb} from './ActiveVideoWebContext' import {useActiveVideoWeb} from './ActiveVideoWebContext'
import * as VideoFallback from './VideoEmbedInner/VideoFallback' import * as VideoFallback from './VideoEmbedInner/VideoFallback'
@@ -37,7 +37,7 @@ const noop = () => {}
*/ */
const MIN_CARD_WIDTH = 280 const MIN_CARD_WIDTH = 280
export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { export function VideoEmbed({embed}: {embed: app.bsky.embed.video.View}) {
const t = useTheme() const t = useTheme()
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
const { const {
@@ -284,7 +284,7 @@ function VideoError({
error, error,
retry, retry,
}: { }: {
embed: AppBskyEmbedVideo.View embed: app.bsky.embed.video.View
error: unknown error: unknown
retry: () => void retry: () => void
}) { }) {
+1 -2
View File
@@ -1,6 +1,5 @@
import {useCallback, useMemo} from 'react' import {useCallback, useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type $Typed} from '@atproto/lex' import {type $Typed} from '@atproto/lex'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {moderatePost} from '@bsky.app/sdk/moderation' import {moderatePost} from '@bsky.app/sdk/moderation'
@@ -259,7 +258,7 @@ export function QuoteEmbed({
linkDisabled?: boolean linkDisabled?: boolean
}) { }) {
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const quote = useMemo<$Typed<AppBskyFeedDefs.PostView>>( const quote = useMemo<$Typed<app.bsky.feed.defs.PostView>>(
() => ({ () => ({
...embed.view, ...embed.view,
$type: 'app.bsky.feed.defs#postView', $type: 'app.bsky.feed.defs#postView',
+4 -3
View File
@@ -1,7 +1,8 @@
import {type StyleProp, type ViewStyle} from 'react-native' import {type StyleProp, type ViewStyle} from 'react-native'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {type ModerationDecision} from '@bsky.app/sdk/moderation'
import {type app} from '#/lexicons'
export enum PostEmbedViewContext { export enum PostEmbedViewContext {
ThreadHighlighted = 'ThreadHighlighted', ThreadHighlighted = 'ThreadHighlighted',
Feed = 'Feed', Feed = 'Feed',
@@ -20,10 +21,10 @@ export type CommonProps = {
* events (post:photoEmbed:*). When the embed has no owning post (e.g. * events (post:photoEmbed:*). When the embed has no owning post (e.g.
* composer previews), leave this undefined and no events will be emitted. * composer previews), leave this undefined and no events will be emitted.
*/ */
post?: AppBskyFeedDefs.PostView post?: app.bsky.feed.defs.PostView
feedDescriptor?: string feedDescriptor?: string
} }
export type EmbedProps = CommonProps & { export type EmbedProps = CommonProps & {
embed?: AppBskyFeedDefs.PostView['embed'] embed?: app.bsky.feed.defs.PostView['embed']
} }
+2 -3
View File
@@ -1,6 +1,5 @@
import {useCallback, useMemo} from 'react' import {useCallback, useMemo} from 'react'
import {Platform, type StyleProp, type TextStyle, View} from 'react-native' import {Platform, type StyleProp, type TextStyle, View} from 'react-native'
import {type AppBskyFeedDefs, type AppBskyFeedPost} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {HITSLOP_30} from '#/lib/constants' import {HITSLOP_30} from '#/lib/constants'
@@ -39,7 +38,7 @@ export function TranslatedPost({
postTextStyle = a.text_md, postTextStyle = a.text_md,
}: { }: {
hideTranslateLink?: boolean hideTranslateLink?: boolean
post: AppBskyFeedDefs.PostView post: app.bsky.feed.defs.PostView
postTextStyle?: StyleProp<TextStyle> postTextStyle?: StyleProp<TextStyle>
}) { }) {
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
@@ -47,7 +46,7 @@ export function TranslatedPost({
key: post.uri, key: post.uri,
}) })
const record = useMemo<AppBskyFeedPost.Record | undefined>(() => { const record = useMemo<app.bsky.feed.post.Main | undefined>(() => {
return bsky.isType(app.bsky.feed.post, post.record) return bsky.isType(app.bsky.feed.post, post.record)
? post.record ? post.record
: undefined : undefined
@@ -1,6 +1,5 @@
import {memo} from 'react' import {memo} from 'react'
import {type Insets} from 'react-native' import {type Insets} from 'react-native'
import {type AppBskyFeedDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -15,6 +14,7 @@ import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
import * as toast from '#/components/Toast' import * as toast from '#/components/Toast'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import {PostControlButton, PostControlButtonIcon} from './PostControlButton' import {PostControlButton, PostControlButtonIcon} from './PostControlButton'
export const BookmarkButton = memo(function BookmarkButton({ export const BookmarkButton = memo(function BookmarkButton({
@@ -23,7 +23,7 @@ export const BookmarkButton = memo(function BookmarkButton({
logContext, logContext,
hitSlop, hitSlop,
}: { }: {
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<app.bsky.feed.defs.PostView>
big?: boolean big?: boolean
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
hitSlop?: Insets hitSlop?: Insets
@@ -6,11 +6,6 @@ import {
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import * as Clipboard from 'expo-clipboard' import * as Clipboard from 'expo-clipboard'
import {
type AppBskyFeedDefs,
type AppBskyFeedPost,
type AppBskyFeedThreadgate,
} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {plural} from '@lingui/core/macro' import {plural} from '@lingui/core/macro'
@@ -97,6 +92,7 @@ import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_INTERNAL} from '#/env' import {IS_INTERNAL} from '#/env'
import {type app} from '#/lexicons'
let PostMenuItems = ({ let PostMenuItems = ({
post, post,
@@ -110,17 +106,17 @@ let PostMenuItems = ({
forceGoogleTranslate, forceGoogleTranslate,
}: { }: {
testID: string testID: string
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<app.bsky.feed.defs.PostView>
postFeedContext: string | undefined postFeedContext: string | undefined
postReqId: string | undefined postReqId: string | undefined
record: AppBskyFeedPost.Record record: app.bsky.feed.post.Main
richText: RichTextAPI richText: RichTextAPI
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
hitSlop?: PressableProps['hitSlop'] hitSlop?: PressableProps['hitSlop']
size?: 'lg' | 'md' | 'sm' size?: 'lg' | 'md' | 'sm'
timestamp: string timestamp: string
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: app.bsky.feed.threadgate.Main
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
forceGoogleTranslate: boolean forceGoogleTranslate: boolean
}): React.ReactNode => { }): React.ReactNode => {
@@ -1,10 +1,5 @@
import {memo, useMemo, useState} from 'react' import {memo, useMemo, useState} from 'react'
import {type Insets} from 'react-native' import {type Insets} from 'react-native'
import {
type AppBskyFeedDefs,
type AppBskyFeedPost,
type AppBskyFeedThreadgate,
} from '@atproto/api'
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
@@ -13,6 +8,7 @@ import {EventStopper} from '#/view/com/util/EventStopper'
import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {useMenuControl} from '#/components/Menu' import {useMenuControl} from '#/components/Menu'
import {type app} from '#/lexicons'
import {PostControlButton, PostControlButtonIcon} from '../PostControlButton' import {PostControlButton, PostControlButtonIcon} from '../PostControlButton'
import {PostMenuItems} from './PostMenuItems' import {PostMenuItems} from './PostMenuItems'
@@ -32,15 +28,15 @@ let PostMenuButton = ({
forceGoogleTranslate, forceGoogleTranslate,
}: { }: {
testID: string testID: string
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<app.bsky.feed.defs.PostView>
postFeedContext: string | undefined postFeedContext: string | undefined
postReqId: string | undefined postReqId: string | undefined
big?: boolean big?: boolean
record: AppBskyFeedPost.Record record: app.bsky.feed.post.Main
richText: RichTextAPI richText: RichTextAPI
timestamp: string timestamp: string
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: app.bsky.feed.threadgate.Main
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void
hitSlop?: Insets hitSlop?: Insets
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
forceGoogleTranslate: boolean forceGoogleTranslate: boolean
@@ -1,5 +1,4 @@
import {ScrollView, View} from 'react-native' import {ScrollView, View} from 'react-native'
import {type ChatBskyActorDefs} from '@atproto/api'
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -22,6 +21,7 @@ import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util'
import {ProfileBadges} from '#/components/ProfileBadges' import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type chat} from '#/lexicons'
import {toLex} from '#/types/bsky' import {toLex} from '#/types/bsky'
export function RecentChats({ export function RecentChats({
@@ -114,7 +114,7 @@ function RecentChatItem({
onPress: () => void onPress: () => void
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
convo: ConvoWithDetails convo: ConvoWithDetails
primaryMember: ChatBskyActorDefs.ProfileViewBasic primaryMember: chat.bsky.actor.defs.ProfileViewBasic
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
@@ -1,22 +1,18 @@
import {type PressableProps, type StyleProp, type ViewStyle} from 'react-native' import {type PressableProps, type StyleProp, type ViewStyle} from 'react-native'
import {
type AppBskyFeedDefs,
type AppBskyFeedPost,
type AppBskyFeedThreadgate,
} from '@atproto/api'
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {type Shadow} from '#/state/cache/post-shadow' import {type Shadow} from '#/state/cache/post-shadow'
import {type app} from '#/lexicons'
export interface ShareMenuItemsProps { export interface ShareMenuItemsProps {
testID: string testID: string
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<app.bsky.feed.defs.PostView>
record: AppBskyFeedPost.Record record: app.bsky.feed.post.Main
richText: RichTextAPI richText: RichTextAPI
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
hitSlop?: PressableProps['hitSlop'] hitSlop?: PressableProps['hitSlop']
size?: 'lg' | 'md' | 'sm' size?: 'lg' | 'md' | 'sm'
timestamp: string timestamp: string
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: app.bsky.feed.threadgate.Main
onShare: () => void onShare: () => void
} }
@@ -1,10 +1,5 @@
import {memo, useMemo, useState} from 'react' import {memo, useMemo, useState} from 'react'
import {type Insets} from 'react-native' import {type Insets} from 'react-native'
import {
type AppBskyFeedDefs,
type AppBskyFeedPost,
type AppBskyFeedThreadgate,
} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
@@ -21,6 +16,7 @@ import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {useMenuControl} from '#/components/Menu' import {useMenuControl} from '#/components/Menu'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import {PostControlButton, PostControlButtonIcon} from '../PostControlButton' import {PostControlButton, PostControlButtonIcon} from '../PostControlButton'
import {ShareMenuItems} from './ShareMenuItems' import {ShareMenuItems} from './ShareMenuItems'
@@ -37,12 +33,12 @@ let ShareMenuButton = ({
logContext, logContext,
}: { }: {
testID: string testID: string
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<app.bsky.feed.defs.PostView>
big?: boolean big?: boolean
record: AppBskyFeedPost.Record record: app.bsky.feed.post.Main
richText: RichTextAPI richText: RichTextAPI
timestamp: string timestamp: string
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: app.bsky.feed.threadgate.Main
onShare: () => void onShare: () => void
hitSlop?: Insets hitSlop?: Insets
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
+5 -9
View File
@@ -1,10 +1,5 @@
import {memo, useMemo, useState} from 'react' import {memo, useMemo, useState} from 'react'
import {type StyleProp, View, type ViewStyle} from 'react-native' import {type StyleProp, View, type ViewStyle} from 'react-native'
import {
type AppBskyFeedDefs,
type AppBskyFeedPost,
type AppBskyFeedThreadgate,
} from '@atproto/api'
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {plural} from '@lingui/core/macro' import {plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
@@ -29,6 +24,7 @@ import {useFormatPostStatCount} from '#/components/PostControls/util'
import * as Skele from '#/components/Skeleton' import * as Skele from '#/components/Skeleton'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import {BookmarkButton} from './BookmarkButton' import {BookmarkButton} from './BookmarkButton'
import { import {
PostControlButton, PostControlButton,
@@ -57,8 +53,8 @@ let PostControls = ({
forceGoogleTranslate = false, forceGoogleTranslate = false,
}: { }: {
big?: boolean big?: boolean
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<app.bsky.feed.defs.PostView>
record: AppBskyFeedPost.Record record: app.bsky.feed.post.Main
richText: RichTextAPI richText: RichTextAPI
feedContext?: string | undefined feedContext?: string | undefined
reqId?: string | undefined reqId?: string | undefined
@@ -66,8 +62,8 @@ let PostControls = ({
onPressReply: () => void onPressReply: () => void
onPostReply?: (postUri: string | undefined) => void onPostReply?: (postUri: string | undefined) => void
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: app.bsky.feed.threadgate.Main
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void
viaRepost?: {uri: string; cid: string} viaRepost?: {uri: string; cid: string}
variant?: 'compact' | 'normal' | 'large' variant?: 'compact' | 'normal' | 'large'
forceGoogleTranslate?: boolean forceGoogleTranslate?: boolean
@@ -1,6 +1,5 @@
import {memo, useCallback, useEffect, useMemo, useReducer, useRef} from 'react' import {memo, useCallback, useEffect, useMemo, useReducer, useRef} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom' import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom'
import {msg, plural} from '@lingui/core/macro' import {msg, plural} from '@lingui/core/macro'
@@ -39,6 +38,7 @@ import {Text} from '#/components/Typography'
import {IS_WEB_TOUCH_DEVICE} from '#/env' import {IS_WEB_TOUCH_DEVICE} from '#/env'
import {useActorStatus} from '#/features/liveNow' import {useActorStatus} from '#/features/liveNow'
import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog' import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky' import {toLex} from '#/types/bsky'
import {type ProfileHoverCardProps} from './types' import {type ProfileHoverCardProps} from './types'
@@ -416,7 +416,7 @@ function Inner({
moderationOpts, moderationOpts,
hide, hide,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed profile: app.bsky.actor.defs.ProfileViewDetailed
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
hide: () => void hide: () => void
}) { }) {
@@ -207,7 +207,7 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
type: 'profile', type: 'profile',
// Don't share identity across tabs or typing attempts // Don't share identity across tabs or typing attempts
key: resultsKey + ':' + profile.did, key: resultsKey + ':' + profile.did,
profile, profile: profile as bsky.profile.AnyProfileView,
}) })
} }
} }
+7 -22
View File
@@ -1,7 +1,6 @@
import {useMemo} from 'react' import {useMemo} from 'react'
import {type StyleProp, type TextStyle} from 'react-native' import {type StyleProp, type TextStyle} from 'react-native'
import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {RichText as SdkRichText} from '@bsky.app/sdk/richtext'
import {toShortUrl} from '#/lib/strings/url-helpers' import {toShortUrl} from '#/lib/strings/url-helpers'
import {atoms as a, flatten, type TextStyleProp} from '#/alf' import {atoms as a, flatten, type TextStyleProp} from '#/alf'
@@ -10,6 +9,8 @@ import {InlineLinkText, type LinkProps} from '#/components/Link'
import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {RichTextTag} from '#/components/RichTextTag' import {RichTextTag} from '#/components/RichTextTag'
import {Text, type TextProps} from '#/components/Typography' import {Text, type TextProps} from '#/components/Typography'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
const WORD_WRAP = {wordWrap: 1} const WORD_WRAP = {wordWrap: 1}
// lifted from facet detection in `RichText` impl, _without_ `gm` flags // lifted from facet detection in `RichText` impl, _without_ `gm` flags
@@ -18,16 +19,7 @@ const URL_REGEX =
export type RichTextProps = TextStyleProp & export type RichTextProps = TextStyleProp &
Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & { Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & {
/* value: RichTextAPI | string
* TODO(phase4): drop the `SdkRichText` arm and normalization below, keeping
* only the SDK RichText. Interim dual-world acceptance: the migrated
* `useRichText` hook now produces an `@bsky.app/sdk/richtext` RichText,
* while ~100 call sites still pass the old `@atproto/api` RichText produced
* elsewhere. We accept both and normalize an SDK instance into the old
* RichText below (its `.facets` flow new->old without a cast) so the render
* body stays single-typed until the RichText UI callers migrate (Task 7).
*/
value: RichTextAPI | SdkRichText | string
testID?: string testID?: string
numberOfLines?: number numberOfLines?: number
disableLinks?: boolean disableLinks?: boolean
@@ -69,13 +61,6 @@ export function RichText({
const richText = useMemo(() => { const richText = useMemo(() => {
if (value instanceof RichTextAPI) { if (value instanceof RichTextAPI) {
return value return value
} else if (value instanceof SdkRichText) {
/*
* Normalize the SDK RichText into the old one this component renders
* against. `.facets` are structurally identical modulo branded strings
* (new->old assigns), so they carry over without a cast.
*/
return new RichTextAPI({text: value.text, facets: value.facets})
} else { } else {
const rt = new RichTextAPI({text: value}) const rt = new RichTextAPI({text: value})
rt.detectFacetsWithoutResolution() rt.detectFacetsWithoutResolution()
@@ -134,7 +119,7 @@ export function RichText({
if ( if (
mention && mention &&
(disableMentionFacetValidation || (disableMentionFacetValidation ||
AppBskyRichtextFacet.validateMention(mention).success) && bsky.matches(app.bsky.richtext.facet.mention, mention)) &&
!disableLinks !disableLinks
) { ) {
els.push( els.push(
@@ -151,7 +136,7 @@ export function RichText({
</InlineLinkText> </InlineLinkText>
</ProfileHoverCard>, </ProfileHoverCard>,
) )
} else if (link && AppBskyRichtextFacet.validateLink(link).success) { } else if (link && bsky.matches(app.bsky.richtext.facet.link, link)) {
const isValidLink = URL_REGEX.test(link.uri) const isValidLink = URL_REGEX.test(link.uri)
if (!isValidLink || disableLinks) { if (!isValidLink || disableLinks) {
els.push(toShortUrl(segment.text)) els.push(toShortUrl(segment.text))
@@ -176,7 +161,7 @@ export function RichText({
!disableLinks && !disableLinks &&
enableTags && enableTags &&
tag && tag &&
AppBskyRichtextFacet.validateTag(tag).success bsky.matches(app.bsky.richtext.facet.tag, tag)
) { ) {
els.push( els.push(
<RichTextTag <RichTextTag
@@ -1,6 +1,5 @@
import {forwardRef, useCallback, useImperativeHandle, useState} from 'react' import {forwardRef, useCallback, useImperativeHandle, useState} from 'react'
import {type ListRenderItemInfo, View} from 'react-native' import {type ListRenderItemInfo, View} from 'react-native'
import {type AppBskyFeedDefs} from '@atproto/api'
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
import {List, type ListRef} from '#/view/com/util/List' import {List, type ListRef} from '#/view/com/util/List'
@@ -8,13 +7,14 @@ import {type SectionRef} from '#/screens/Profile/Sections/types'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import * as FeedCard from '#/components/FeedCard' import * as FeedCard from '#/components/FeedCard'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import {type app} from '#/lexicons'
function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { function keyExtractor(item: app.bsky.feed.defs.GeneratorView) {
return item.uri return item.uri
} }
interface ProfilesListProps { interface ProfilesListProps {
feeds: AppBskyFeedDefs.GeneratorView[] feeds: app.bsky.feed.defs.GeneratorView[]
headerHeight: number headerHeight: number
scrollElRef: ListRef scrollElRef: ListRef
} }
@@ -39,7 +39,7 @@ export const FeedsList = forwardRef<SectionRef, ProfilesListProps>(
const renderItem = ({ const renderItem = ({
item, item,
index, index,
}: ListRenderItemInfo<AppBskyFeedDefs.GeneratorView>) => { }: ListRenderItemInfo<app.bsky.feed.defs.GeneratorView>) => {
return ( return (
<View <View
style={[ style={[
@@ -1,6 +1,5 @@
import {forwardRef, useCallback, useImperativeHandle, useState} from 'react' import {forwardRef, useCallback, useImperativeHandle, useState} from 'react'
import {type ListRenderItemInfo, View} from 'react-native' import {type ListRenderItemInfo, View} from 'react-native'
import {type AppBskyActorDefs, type AppBskyGraphGetList} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import { import {
@@ -19,15 +18,16 @@ import {atoms as a, useTheme} from '#/alf'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {Default as ProfileCard} from '#/components/ProfileCard' import {Default as ProfileCard} from '#/components/ProfileCard'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import {type app} from '#/lexicons'
function keyExtractor(item: AppBskyActorDefs.ProfileView, index: number) { function keyExtractor(item: app.bsky.actor.defs.ProfileView, index: number) {
return `${item.did}-${index}` return `${item.did}-${index}`
} }
interface ProfilesListProps { interface ProfilesListProps {
listUri: string listUri: string
listMembersQuery: UseInfiniteQueryResult< listMembersQuery: UseInfiniteQueryResult<
InfiniteData<AppBskyGraphGetList.OutputSchema> InfiniteData<app.bsky.graph.getList.$OutputBody>
> >
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
headerHeight: number headerHeight: number
@@ -84,7 +84,7 @@ export const ProfilesList = forwardRef<SectionRef, ProfilesListProps>(
const renderItem = ({ const renderItem = ({
item, item,
index, index,
}: ListRenderItemInfo<AppBskyActorDefs.ProfileView>) => { }: ListRenderItemInfo<app.bsky.actor.defs.ProfileView>) => {
return ( return (
<View <View
style={[ style={[
@@ -6,7 +6,6 @@ import {
View, View,
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import {type AppBskyGraphDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -37,6 +36,7 @@ import * as Prompt from '#/components/Prompt'
import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard' import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
import {type app} from '#/lexicons'
interface SectionRef { interface SectionRef {
scrollToTop: () => void scrollToTop: () => void
@@ -57,7 +57,7 @@ interface ProfileFeedgensProps {
emptyStateIcon?: React.ComponentType<any> | React.ReactElement emptyStateIcon?: React.ComponentType<any> | React.ReactElement
} }
function keyExtractor(item: AppBskyGraphDefs.StarterPackViewBasic) { function keyExtractor(item: app.bsky.graph.defs.StarterPackViewBasic) {
return item.uri return item.uri
} }
@@ -147,7 +147,7 @@ export function ProfileStarterPacks({
({ ({
item, item,
index, index,
}: ListRenderItemInfo<AppBskyGraphDefs.StarterPackViewBasic>) => { }: ListRenderItemInfo<app.bsky.graph.defs.StarterPackViewBasic>) => {
return ( return (
<View <View
style={[ style={[
+1 -2
View File
@@ -3,7 +3,6 @@ import {View} from 'react-native'
// @ts-expect-error missing types // @ts-expect-error missing types
import QRCode from 'react-native-qrcode-styled' import QRCode from 'react-native-qrcode-styled'
import type ViewShot from 'react-native-view-shot' import type ViewShot from 'react-native-view-shot'
import {type AppBskyGraphDefs} from '@atproto/api'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
import {Logo} from '#/view/icons/Logo' import {Logo} from '#/view/icons/Logo'
@@ -25,7 +24,7 @@ export function QrCode({
link, link,
ref, ref,
}: { }: {
starterPack: AppBskyGraphDefs.StarterPackView starterPack: app.bsky.graph.defs.StarterPackView
link: string link: string
ref: React.Ref<ViewShot> ref: React.Ref<ViewShot>
}) { }) {
+1 -2
View File
@@ -3,7 +3,6 @@ import {View} from 'react-native'
import type ViewShot from 'react-native-view-shot' import type ViewShot from 'react-native-view-shot'
import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library' import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library'
import * as Sharing from 'expo-sharing' import * as Sharing from 'expo-sharing'
import {type AppBskyGraphDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -29,7 +28,7 @@ export function QrCodeDialog({
link, link,
control, control,
}: { }: {
starterPack: AppBskyGraphDefs.StarterPackView starterPack: app.bsky.graph.defs.StarterPackView
link?: string link?: string
control: DialogControlProps control: DialogControlProps
}) { }) {
+2 -2
View File
@@ -1,6 +1,5 @@
import {View} from 'react-native' import {View} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {type AppBskyGraphDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -19,9 +18,10 @@ import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import {type app} from '#/lexicons'
interface Props { interface Props {
starterPack: AppBskyGraphDefs.StarterPackView starterPack: app.bsky.graph.defs.StarterPackView
link?: string link?: string
imageLoaded?: boolean imageLoaded?: boolean
qrDialogControl: DialogControlProps qrDialogControl: DialogControlProps
@@ -1,7 +1,6 @@
import {useRef} from 'react' import {useRef} from 'react'
import {type ListRenderItemInfo} from 'react-native' import {type ListRenderItemInfo} from 'react-native'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -22,9 +21,10 @@ import {
} from '#/components/StarterPack/Wizard/WizardListCard' } from '#/components/StarterPack/Wizard/WizardListCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {type app} from '#/lexicons'
function keyExtractor( function keyExtractor(
item: AppBskyActorDefs.ProfileViewBasic | AppBskyFeedDefs.GeneratorView, item: app.bsky.actor.defs.ProfileViewBasic | app.bsky.feed.defs.GeneratorView,
index: number, index: number,
) { ) {
return `${item.did}-${index}` return `${item.did}-${index}`
@@ -41,7 +41,7 @@ export function WizardEditListDialog({
state: WizardState state: WizardState
dispatch: (action: WizardAction) => void dispatch: (action: WizardAction) => void
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
profile: AppBskyActorDefs.ProfileViewDetailed profile: app.bsky.actor.defs.ProfileViewDetailed
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
@@ -1,5 +1,4 @@
import {Keyboard, View} from 'react-native' import {Keyboard, View} from 'react-native'
import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api'
import { import {
moderateFeedGenerator, moderateFeedGenerator,
moderateProfile, moderateProfile,
@@ -25,6 +24,7 @@ import * as Toggle from '#/components/forms/Toggle'
import {Checkbox} from '#/components/forms/Toggle' import {Checkbox} from '#/components/forms/Toggle'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky' import {toLex} from '#/types/bsky'
@@ -41,8 +41,8 @@ function WizardListCard({
}: { }: {
type: 'user' | 'algo' type: 'user' | 'algo'
btnType: 'checkbox' | 'remove' btnType: 'checkbox' | 'remove'
profile?: AppBskyActorDefs.ProfileViewBasic profile?: app.bsky.actor.defs.ProfileViewBasic
feed?: AppBskyFeedDefs.GeneratorView feed?: app.bsky.feed.defs.GeneratorView
displayName: string displayName: string
subtitle: string subtitle: string
onPress: () => void onPress: () => void
@@ -186,7 +186,7 @@ export function WizardFeedCard({
moderationOpts, moderationOpts,
}: { }: {
btnType: 'checkbox' | 'remove' btnType: 'checkbox' | 'remove'
generator: AppBskyFeedDefs.GeneratorView generator: app.bsky.feed.defs.GeneratorView
state: WizardState state: WizardState
dispatch: (action: WizardAction) => void dispatch: (action: WizardAction) => void
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
+5 -10
View File
@@ -2,11 +2,6 @@ import {useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {LinearGradient} from 'expo-linear-gradient' import {LinearGradient} from 'expo-linear-gradient'
import {
type AppBskyActorDefs,
AppBskyEmbedVideo,
type AppBskyFeedDefs,
} from '@atproto/api'
import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {type ModerationDecision} from '@bsky.app/sdk/moderation'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
@@ -42,7 +37,7 @@ export function VideoPostCard({
moderation, moderation,
onInteract, onInteract,
}: { }: {
post: AppBskyFeedDefs.PostView post: app.bsky.feed.defs.PostView
sourceContext: VideoFeedSourceContext sourceContext: VideoFeedSourceContext
moderation: ModerationDecision moderation: ModerationDecision
/** /**
@@ -75,7 +70,7 @@ export function VideoPostCard({
* Filtering should be done at a higher level, such as `PostFeed` or * Filtering should be done at a higher level, such as `PostFeed` or
* `PostFeedVideoGridRow`, but we need to protect here as well. * `PostFeedVideoGridRow`, but we need to protect here as well.
*/ */
if (!AppBskyEmbedVideo.isView(embed)) return null if (!bsky.isType(app.bsky.embed.video.view, embed)) return null
const author = post.author const author = post.author
const text = bsky.isType(app.bsky.feed.post, post.record) const text = bsky.isType(app.bsky.feed.post, post.record)
@@ -272,7 +267,7 @@ export function VideoPostCardPlaceholder() {
export function VideoPostCardTextPlaceholder({ export function VideoPostCardTextPlaceholder({
author, author,
}: { }: {
author?: AppBskyActorDefs.ProfileViewBasic author?: app.bsky.actor.defs.ProfileViewBasic
}) { }) {
const t = useTheme() const t = useTheme()
@@ -352,7 +347,7 @@ export function CompactVideoPostCard({
moderation, moderation,
onInteract, onInteract,
}: { }: {
post: AppBskyFeedDefs.PostView post: app.bsky.feed.defs.PostView
sourceContext: VideoFeedSourceContext sourceContext: VideoFeedSourceContext
moderation: ModerationDecision moderation: ModerationDecision
/** /**
@@ -383,7 +378,7 @@ export function CompactVideoPostCard({
* Filtering should be done at a higher level, such as `PostFeed` or * Filtering should be done at a higher level, such as `PostFeed` or
* `PostFeedVideoGridRow`, but we need to protect here as well. * `PostFeedVideoGridRow`, but we need to protect here as well.
*/ */
if (!AppBskyEmbedVideo.isView(embed)) return null if (!bsky.isType(app.bsky.embed.video.view, embed)) return null
const likeCount = post?.likeCount ?? 0 const likeCount = post?.likeCount ?? 0
const showLikeCount = false const showLikeCount = false
+5 -6
View File
@@ -6,7 +6,6 @@ import {
View, View,
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -38,7 +37,7 @@ import {app} from '#/lexicons'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
interface WhoCanReplyProps { interface WhoCanReplyProps {
post: AppBskyFeedDefs.PostView post: app.bsky.feed.defs.PostView
isThreadAuthor: boolean isThreadAuthor: boolean
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
} }
@@ -203,7 +202,7 @@ function WhoCanReplyDialog({
embeddingDisabled, embeddingDisabled,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
post: AppBskyFeedDefs.PostView post: app.bsky.feed.defs.PostView
settings: ThreadgateAllowUISetting[] settings: ThreadgateAllowUISetting[]
embeddingDisabled: boolean embeddingDisabled: boolean
}) { }) {
@@ -249,7 +248,7 @@ function Rules({
settings, settings,
embeddingDisabled, embeddingDisabled,
}: { }: {
post: AppBskyFeedDefs.PostView post: app.bsky.feed.defs.PostView
settings: ThreadgateAllowUISetting[] settings: ThreadgateAllowUISetting[]
embeddingDisabled: boolean embeddingDisabled: boolean
}) { }) {
@@ -307,8 +306,8 @@ function Rule({
lists, lists,
}: { }: {
rule: ThreadgateAllowUISetting rule: ThreadgateAllowUISetting
post: AppBskyFeedDefs.PostView post: app.bsky.feed.defs.PostView
lists: AppBskyGraphDefs.ListViewBasic[] | undefined lists: app.bsky.graph.defs.ListViewBasic[] | undefined
}) { }) {
if (rule.type === 'mention') { if (rule.type === 'mention') {
return <Trans>mentioned users</Trans> return <Trans>mentioned users</Trans>
@@ -1,10 +1,6 @@
import {useMemo, useState} from 'react' import {useMemo, useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import { import {type Un$Typed} from '@atproto/lex'
type AppBskyNotificationDefs,
type AppBskyNotificationListActivitySubscriptions,
type Un$Typed,
} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -37,6 +33,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function SubscribeProfileDialog({ export function SubscribeProfileDialog({
@@ -120,7 +117,7 @@ function DialogInner({
error, error,
} = useMutation({ } = useMutation({
mutationFn: async ( mutationFn: async (
activitySubscription: Un$Typed<AppBskyNotificationDefs.ActivitySubscription>, activitySubscription: Un$Typed<app.bsky.notification.defs.ActivitySubscription>,
) => { ) => {
await agent.app.bsky.notification.putActivitySubscription({ await agent.app.bsky.notification.putActivitySubscription({
subject: profile.did, subject: profile.did,
@@ -148,7 +145,7 @@ function DialogInner({
queryClient.setQueryData( queryClient.setQueryData(
RQKEY_getActivitySubscriptions, RQKEY_getActivitySubscriptions,
( (
old?: InfiniteData<AppBskyNotificationListActivitySubscriptions.OutputSchema>, old?: InfiniteData<app.bsky.notification.listActivitySubscriptions.$OutputBody>,
) => { ) => {
if (!old) return old if (!old) return old
return { return {
@@ -308,8 +305,8 @@ function DialogInner({
} }
function parseActivitySubscription( function parseActivitySubscription(
sub?: AppBskyNotificationDefs.ActivitySubscription, sub?: app.bsky.notification.defs.ActivitySubscription,
): Un$Typed<AppBskyNotificationDefs.ActivitySubscription> { ): Un$Typed<app.bsky.notification.defs.ActivitySubscription> {
if (!sub) return {post: false, reply: false} if (!sub) return {post: false, reply: false}
const {post, reply} = sub const {post, reply} = sub
return {post, reply} return {post, reply}
+3 -4
View File
@@ -1,6 +1,5 @@
import {type AppBskyContactDefs} from '@atproto/api'
import {type CountryCode} from '#/lib/international-telephone-codes' import {type CountryCode} from '#/lib/international-telephone-codes'
import {type app} from '#/lexicons'
import {normalizePhoneNumber} from './phone-number' import {normalizePhoneNumber} from './phone-number'
import {type Contact, type Match} from './state' import {type Contact, type Match} from './state'
@@ -70,7 +69,7 @@ export function normalizeContactBook(
export function filterMatchedNumbers( export function filterMatchedNumbers(
contacts: Contact[], contacts: Contact[],
results: AppBskyContactDefs.MatchAndContactIndex[], results: app.bsky.contact.defs.MatchAndContactIndex[],
mapping: Map<number, Contact['id']>, mapping: Map<number, Contact['id']>,
) { ) {
const filteredIds = new Set<Contact['id']>() const filteredIds = new Set<Contact['id']>()
@@ -87,7 +86,7 @@ export function filterMatchedNumbers(
export function getMatchedContacts( export function getMatchedContacts(
contacts: Contact[], contacts: Contact[],
results: AppBskyContactDefs.MatchAndContactIndex[], results: app.bsky.contact.defs.MatchAndContactIndex[],
mapping: Map<number, Contact['id']>, mapping: Map<number, Contact['id']>,
): Array<Match> { ): Array<Match> {
const contactsById = new Map(contacts.map(c => [c.id, c])) const contactsById = new Map(contacts.map(c => [c.id, c]))
+23 -23
View File
@@ -2,11 +2,10 @@ import {useContext} from 'react'
import {Alert, View} from 'react-native' import {Alert, View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import * as Contacts from 'expo-contacts' import * as Contacts from 'expo-contacts'
import { import {type Un$Typed} from '@atproto/lex'
type AppBskyActorProfile, import {type Client} from '@atproto/lex-client'
AppBskyContactImportContacts, import {toDatetimeString} from '@atproto/syntax'
type Un$Typed, import {upsertProfile} from '@bsky.app/sdk'
} from '@atproto/api'
import {msg, t} from '@lingui/core/macro' import {msg, t} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -14,9 +13,10 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
import {uploadBlob} from '#/lib/api' import {uploadBlob} from '#/lib/api'
import {cleanError, isNetworkError} from '#/lib/strings/errors' import {cleanError, isNetworkError} from '#/lib/strings/errors'
import {getErrorName} from '#/lib/xrpc-error'
import {logger} from '#/logger' import {logger} from '#/logger'
import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' import {findContactsStatusQueryKey} from '#/state/queries/find-contacts'
import {type SessionAgent, useAgent} from '#/state/session' import {useAppviewClient, usePdsClient} from '#/state/session'
import { import {
Context as OnboardingContext, Context as OnboardingContext,
type OnboardingAction, type OnboardingAction,
@@ -29,6 +29,7 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons'
import { import {
contactsWithPhoneNumbersOnly, contactsWithPhoneNumbersOnly,
filterMatchedNumbers, filterMatchedNumbers,
@@ -53,7 +54,8 @@ export function GetContacts({
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const agent = useAgent() const pdsClient = usePdsClient()
const appviewClient = useAppviewClient()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const gutters = useGutters([0, 'wide']) const gutters = useGutters([0, 'wide'])
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -71,7 +73,7 @@ export function GetContacts({
*/ */
if (context === 'Onboarding' && maybeOnboardingContext) { if (context === 'Onboarding' && maybeOnboardingContext) {
try { try {
await createProfileRecord(agent, maybeOnboardingContext) await createProfileRecord(pdsClient, maybeOnboardingContext)
} catch (error) { } catch (error) {
logger.debug('Error creating profile record:', {safeMessage: error}) logger.debug('Error creating profile record:', {safeMessage: error})
} }
@@ -84,13 +86,13 @@ export function GetContacts({
) )
if (phoneNumbers.length > 0) { if (phoneNumbers.length > 0) {
const res = await agent.app.bsky.contact.importContacts({ const res = await appviewClient.call(app.bsky.contact.importContacts, {
token: state.token, token: state.token,
contacts: phoneNumbers.slice(0, MAX_UPLOAD_COUNT), contacts: phoneNumbers.slice(0, MAX_UPLOAD_COUNT),
}) })
return { return {
matches: res.data.matchesAndContactIndexes, matches: res.matchesAndContactIndexes,
indexToContactId, indexToContactId,
} }
} else { } else {
@@ -147,18 +149,14 @@ export function GetContacts({
), ),
{type: 'error'}, {type: 'error'},
) )
} else if ( } else if (getErrorName(err) === 'TooManyContacts') {
err instanceof AppBskyContactImportContacts.TooManyContactsError
) {
Toast.show( Toast.show(
_( _(
msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`, msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`,
), ),
{type: 'error'}, {type: 'error'},
) )
} else if ( } else if (getErrorName(err) === 'InvalidToken') {
err instanceof AppBskyContactImportContacts.InvalidTokenError
) {
Toast.show( Toast.show(
_( _(
msg`Could not upload contacts. You need to re-verify your phone number to proceed`, msg`Could not upload contacts. You need to re-verify your phone number to proceed`,
@@ -324,7 +322,7 @@ function showPermissionDeniedAlert() {
* Copied from `#/screens/Onboarding/StepFinished/index.tsx` * Copied from `#/screens/Onboarding/StepFinished/index.tsx`
*/ */
async function createProfileRecord( async function createProfileRecord(
agent: SessionAgent, pdsClient: Client,
onboardingContext: { onboardingContext: {
state: OnboardingState state: OnboardingState
dispatch: React.Dispatch<OnboardingAction> dispatch: React.Dispatch<OnboardingAction>
@@ -333,21 +331,23 @@ async function createProfileRecord(
const profileStepResults = onboardingContext.state.profileStepResults const profileStepResults = onboardingContext.state.profileStepResults
const {imageUri, imageMime} = profileStepResults const {imageUri, imageMime} = profileStepResults
const blobPromise = const blobPromise =
imageUri && imageMime ? uploadBlob(agent, imageUri, imageMime) : undefined imageUri && imageMime
? uploadBlob(pdsClient, imageUri, imageMime)
: undefined
await agent.upsertProfile(async existing => { await pdsClient.call(upsertProfile, async existing => {
let next: Un$Typed<AppBskyActorProfile.Record> = existing ?? {} let next: Un$Typed<app.bsky.actor.profile.Main> = existing ?? {}
if (blobPromise) { if (blobPromise) {
const res = await blobPromise const res = await blobPromise
if (res.data.blob) { if (res.blob) {
next.avatar = res.data.blob next.avatar = res.blob
} }
} }
next.displayName = '' next.displayName = ''
next.createdAt = new Date().toISOString() next.createdAt = toDatetimeString(new Date())
return next return next
}) })
} }
@@ -2,7 +2,6 @@ import {useState} from 'react'
import {Keyboard, View} from 'react-native' import {Keyboard, View} from 'react-native'
import {KeyboardAvoidingView} from 'react-native-keyboard-controller' import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {AppBskyContactStartPhoneVerification} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -14,6 +13,7 @@ import {
getDefaultCountry, getDefaultCountry,
} from '#/lib/international-telephone-codes' } from '#/lib/international-telephone-codes'
import {cleanError, isNetworkError} from '#/lib/strings/errors' import {cleanError, isNetworkError} from '#/lib/strings/errors'
import {getErrorName} from '#/lib/xrpc-error'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import {OnboardingPosition} from '#/screens/Onboarding/Layout' import {OnboardingPosition} from '#/screens/Onboarding/Layout'
@@ -102,14 +102,9 @@ export function PhoneInput({
msg`A network error occurred. Please check your internet connection`, msg`A network error occurred. Please check your internet connection`,
), ),
) )
} else if ( } else if (getErrorName(err) === 'RateLimitExceeded') {
err instanceof
AppBskyContactStartPhoneVerification.RateLimitExceededError
) {
setError(_(msg`Rate limit exceeded. Please try again later.`)) setError(_(msg`Rate limit exceeded. Please try again later.`))
} else if ( } else if (getErrorName(err) === 'InvalidPhone') {
err instanceof AppBskyContactStartPhoneVerification.InvalidPhoneError
) {
setError( setError(
_( _(
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
@@ -1,9 +1,5 @@
import {useEffect, useMemo, useState} from 'react' import {useEffect, useMemo, useState} from 'react'
import {Text as NestedText, View} from 'react-native' import {Text as NestedText, View} from 'react-native'
import {
AppBskyContactStartPhoneVerification,
AppBskyContactVerifyPhone,
} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -11,6 +7,7 @@ import {useMutation} from '@tanstack/react-query'
import {clamp} from '#/lib/numbers' import {clamp} from '#/lib/numbers'
import {cleanError, isNetworkError} from '#/lib/strings/errors' import {cleanError, isNetworkError} from '#/lib/strings/errors'
import {getErrorName} from '#/lib/xrpc-error'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import {OnboardingPosition} from '#/screens/Onboarding/Layout' import {OnboardingPosition} from '#/screens/Onboarding/Layout'
@@ -99,13 +96,13 @@ export function VerifyNumber({
msg`A network error occurred. Please check your internet connection.`, msg`A network error occurred. Please check your internet connection.`,
), ),
}) })
} else if (err instanceof AppBskyContactVerifyPhone.InvalidCodeError) { } else if (getErrorName(err) === 'InvalidCode') {
setError({ setError({
retryable: true, retryable: true,
isResendError: true, isResendError: true,
message: _(msg`This code is invalid. Resend to get a new code.`), message: _(msg`This code is invalid. Resend to get a new code.`),
}) })
} else if (err instanceof AppBskyContactVerifyPhone.InvalidPhoneError) { } else if (getErrorName(err) === 'InvalidPhone') {
setError({ setError({
retryable: false, retryable: false,
isResendError: false, isResendError: false,
@@ -113,9 +110,7 @@ export function VerifyNumber({
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
), ),
}) })
} else if ( } else if (getErrorName(err) === 'RateLimitExceeded') {
err instanceof AppBskyContactVerifyPhone.RateLimitExceededError
) {
setError({ setError({
retryable: true, retryable: true,
isResendError: false, isResendError: false,
@@ -155,9 +150,7 @@ export function VerifyNumber({
msg`A network error occurred. Please check your internet connection.`, msg`A network error occurred. Please check your internet connection.`,
), ),
}) })
} else if ( } else if (getErrorName(err) === 'InvalidPhone') {
err instanceof AppBskyContactStartPhoneVerification.InvalidPhoneError
) {
setError({ setError({
retryable: false, retryable: false,
isResendError: true, isResendError: true,
@@ -165,10 +158,7 @@ export function VerifyNumber({
msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`,
), ),
}) })
} else if ( } else if (getErrorName(err) === 'RateLimitExceeded') {
err instanceof
AppBskyContactStartPhoneVerification.RateLimitExceededError
) {
setError({ setError({
retryable: true, retryable: true,
isResendError: true, isResendError: true,
@@ -20,7 +20,12 @@ import {
optimisticRemoveMatch, optimisticRemoveMatch,
useMatchesPassthroughQuery, useMatchesPassthroughQuery,
} from '#/state/queries/find-contacts' } from '#/state/queries/find-contacts'
import {useAgent, useSession} from '#/state/session' import {
useAgent,
useAppviewClient,
usePdsClient,
useSession,
} from '#/state/session'
import {List, type ListMethods} from '#/view/com/util/List' import {List, type ListMethods} from '#/view/com/util/List'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import {OnboardingPosition} from '#/screens/Onboarding/Layout' import {OnboardingPosition} from '#/screens/Onboarding/Layout'
@@ -90,6 +95,8 @@ export function ViewMatches({
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const agent = useAgent()
const pdsClient = usePdsClient()
const appviewClient = useAppviewClient()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const listRef = useRef<ListMethods>(null) const listRef = useRef<ListMethods>(null)
@@ -124,7 +131,10 @@ export function ViewMatches({
}) })
} }
const uris = await wait(500, bulkWriteFollows(agent, followableDids)) const uris = await wait(
500,
bulkWriteFollows(pdsClient, appviewClient, followableDids),
)
for (const did of followableDids) { for (const did of followableDids) {
const uri = uris.get(did) const uri = uris.get(did)
+3 -3
View File
@@ -1,6 +1,5 @@
import {memo, useEffect, useMemo, useState} from 'react' import {memo, useEffect, useMemo, useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs, type AppBskyFeedPost} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -21,15 +20,16 @@ import {
} from '#/components/icons/Chevron' } from '#/components/icons/Chevron'
import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type app} from '#/lexicons'
export type ColorModeValues = 'system' | 'light' | 'dark' export type ColorModeValues = 'system' | 'light' | 'dark'
type EmbedDialogProps = { type EmbedDialogProps = {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
postAuthor: AppBskyActorDefs.ProfileViewBasic postAuthor: app.bsky.actor.defs.ProfileViewBasic
postCid: string postCid: string
postUri: string postUri: string
record: AppBskyFeedPost.Record record: app.bsky.feed.post.Main
timestamp: string timestamp: string
} }
+10 -8
View File
@@ -1,6 +1,7 @@
import {useCallback, useState} from 'react' import {useCallback, useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' import {type DatetimeString, toDatetimeString} from '@atproto/syntax'
import {sanitizeMutedWordValue} from '@bsky.app/sdk/utils'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -35,6 +36,7 @@ import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
const ONE_DAY = 24 * 60 * 60 * 1000 const ONE_DAY = 24 * 60 * 60 * 1000
@@ -68,20 +70,20 @@ function MutedWordsInner() {
const sanitizedValue = sanitizeMutedWordValue(field) const sanitizedValue = sanitizeMutedWordValue(field)
const surfaces = ['tag', targets.includes('content') && 'content'].filter( const surfaces = ['tag', targets.includes('content') && 'content'].filter(
Boolean, Boolean,
) as AppBskyActorDefs.MutedWord['targets'] ) as app.bsky.actor.defs.MutedWord['targets']
const actorTarget = excludeFollowing ? 'exclude-following' : 'all' const actorTarget = excludeFollowing ? 'exclude-following' : 'all'
const now = Date.now() const now = Date.now()
const rawDuration = durations.at(0) const rawDuration = durations.at(0)
// undefined evaluates to 'forever' // undefined evaluates to 'forever'
let duration: string | undefined let duration: DatetimeString | undefined
if (rawDuration === '24_hours') { if (rawDuration === '24_hours') {
duration = new Date(now + ONE_DAY).toISOString() duration = toDatetimeString(new Date(now + ONE_DAY))
} else if (rawDuration === '7_days') { } else if (rawDuration === '7_days') {
duration = new Date(now + 7 * ONE_DAY).toISOString() duration = toDatetimeString(new Date(now + 7 * ONE_DAY))
} else if (rawDuration === '30_days') { } else if (rawDuration === '30_days') {
duration = new Date(now + 30 * ONE_DAY).toISOString() duration = toDatetimeString(new Date(now + 30 * ONE_DAY))
} }
if (!sanitizedValue || !surfaces.length) { if (!sanitizedValue || !surfaces.length) {
@@ -421,7 +423,7 @@ function MutedWordsInner() {
function MutedWordRow({ function MutedWordRow({
style, style,
word, word,
}: ViewStyleProp & {word: AppBskyActorDefs.MutedWord}) { }: ViewStyleProp & {word: app.bsky.actor.defs.MutedWord}) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {isPending, mutateAsync: removeMutedWord} = useRemoveMutedWordMutation() const {isPending, mutateAsync: removeMutedWord} = useRemoveMutedWordMutation()
@@ -440,7 +442,7 @@ function MutedWordRow({
updateMutedWord({ updateMutedWord({
...word, ...word,
expiresAt: days expiresAt: days
? new Date(Date.now() + days * ONE_DAY).toISOString() ? toDatetimeString(new Date(Date.now() + days * ONE_DAY))
: undefined, : undefined,
}) })
} }
@@ -1,6 +1,5 @@
import {useCallback, useMemo, useState} from 'react' import {useCallback, useMemo, useState} from 'react'
import {LayoutAnimation, Text as NestedText, View} from 'react-native' import {LayoutAnimation, Text as NestedText, View} from 'react-native'
import {type AppBskyFeedDefs, type AppBskyFeedPostgate} from '@atproto/api'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -33,7 +32,7 @@ import {
PostThreadContextProvider, PostThreadContextProvider,
usePostThreadContext, usePostThreadContext,
} from '#/state/queries/usePostThread' } from '#/state/queries/usePostThread'
import {useAgent, useSession} from '#/state/session' import {usePdsClient, useSession} from '#/state/session'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme, web} from '#/alf' import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -50,6 +49,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
import {type app} from '#/lexicons'
export type PostInteractionSettingsFormProps = { export type PostInteractionSettingsFormProps = {
canSave?: boolean canSave?: boolean
@@ -60,8 +60,8 @@ export type PostInteractionSettingsFormProps = {
persist?: boolean persist?: boolean
onChangePersist?: (v: boolean) => void onChangePersist?: (v: boolean) => void
postgate: AppBskyFeedPostgate.Record postgate: app.bsky.feed.postgate.Main
onChangePostgate: (v: AppBskyFeedPostgate.Record) => void onChangePostgate: (v: app.bsky.feed.postgate.Main) => void
threadgateAllowUISettings: ThreadgateAllowUISetting[] threadgateAllowUISettings: ThreadgateAllowUISetting[]
onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void
@@ -132,10 +132,10 @@ export type PostInteractionSettingsDialogProps = {
*/ */
rootPostUri: string rootPostUri: string
/** /**
* Optional initial {@link AppBskyFeedDefs.ThreadgateView} to use if we * Optional initial {@link app.bsky.feed.defs.ThreadgateView} to use if we
* happen to have one before opening the settings dialog. * happen to have one before opening the settings dialog.
*/ */
initialThreadgateView?: AppBskyFeedDefs.ThreadgateView initialThreadgateView?: app.bsky.feed.defs.ThreadgateView
} }
/** /**
@@ -175,7 +175,7 @@ export function PostInteractionSettingsDialogControlledInner(
const {mutateAsync: setThreadgateAllow} = useSetThreadgateAllowMutation() const {mutateAsync: setThreadgateAllow} = useSetThreadgateAllowMutation()
const [editedPostgate, setEditedPostgate] = const [editedPostgate, setEditedPostgate] =
useState<AppBskyFeedPostgate.Record>() useState<app.bsky.feed.postgate.Main>()
const [editedAllowUISettings, setEditedAllowUISettings] = const [editedAllowUISettings, setEditedAllowUISettings] =
useState<ThreadgateAllowUISetting[]>() useState<ThreadgateAllowUISetting[]>()
@@ -694,7 +694,7 @@ export function usePrefetchPostInteractionSettings({
}) { }) {
const ax = useAnalytics() const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const pdsClient = usePdsClient()
const getPost = useGetPost() const getPost = useGetPost()
return useCallback(async () => { return useCallback(async () => {
@@ -703,7 +703,7 @@ export function usePrefetchPostInteractionSettings({
queryClient.prefetchQuery({ queryClient.prefetchQuery({
queryKey: createPostgateQueryKey(postUri), queryKey: createPostgateQueryKey(postUri),
queryFn: () => queryFn: () =>
getPostgateRecord({agent, postUri}).then(res => res ?? null), getPostgateRecord({pdsClient, postUri}).then(res => res ?? null),
staleTime: STALE.SECONDS.THIRTY, staleTime: STALE.SECONDS.THIRTY,
}), }),
queryClient.prefetchQuery({ queryClient.prefetchQuery({
@@ -720,5 +720,5 @@ export function usePrefetchPostInteractionSettings({
safeMessage: e.message, safeMessage: e.message,
}) })
} }
}, [ax, queryClient, agent, postUri, rootPostUri, getPost]) }, [ax, queryClient, pdsClient, postUri, rootPostUri, getPost])
} }
+1 -2
View File
@@ -1,6 +1,5 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyGraphGetStarterPacksWithMembership} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro' import {Plural, Trans} from '@lingui/react/macro'
@@ -34,7 +33,7 @@ import {app} from '#/lexicons'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
type StarterPackWithMembership = type StarterPackWithMembership =
AppBskyGraphGetStarterPacksWithMembership.StarterPackWithMembership app.bsky.graph.getStarterPacksWithMembership.StarterPackWithMembership
export type StarterPackDialogProps = { export type StarterPackDialogProps = {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
@@ -1,13 +1,12 @@
import {View} from 'react-native' import {View} from 'react-native'
import {
type $Typed,
type AppBskyGraphDefs,
type AppBskyGraphListitem,
type AppBskyGraphStarterpack,
type ComAtprotoRepoApplyWrites,
} from '@atproto/api'
import {TID} from '@atproto/common-web' import {TID} from '@atproto/common-web'
import {AtUri} from '@atproto/syntax' import {type $Typed} from '@atproto/lex'
import {
type AtIdentifierString,
AtUri,
type AtUriString,
toDatetimeString,
} from '@atproto/syntax'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -20,7 +19,7 @@ import {wait} from '#/lib/async/wait'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {logger} from '#/logger' import {logger} from '#/logger'
import {getAllListMembers} from '#/state/queries/list-members' import {getAllListMembers} from '#/state/queries/list-members'
import {useAgent, useSession} from '#/state/session' import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
import {atoms as a, platform, useTheme, web} from '#/alf' import {atoms as a, platform, useTheme, web} from '#/alf'
import {Admonition} from '#/components/Admonition' import {Admonition} from '#/components/Admonition'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
@@ -29,6 +28,7 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {app, com} from '#/lexicons'
import {CreateOrEditListDialog} from './CreateOrEditListDialog' import {CreateOrEditListDialog} from './CreateOrEditListDialog'
export function CreateListFromStarterPackDialog({ export function CreateListFromStarterPackDialog({
@@ -36,11 +36,12 @@ export function CreateListFromStarterPackDialog({
starterPack, starterPack,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
starterPack: AppBskyGraphDefs.StarterPackView starterPack: app.bsky.graph.defs.StarterPackView
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const agent = useAgent() const pdsClient = usePdsClient()
const appviewClient = useAppviewClient()
const ax = useAnalytics() const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
@@ -48,7 +49,7 @@ export function CreateListFromStarterPackDialog({
const createDialogControl = Dialog.useDialogControl() const createDialogControl = Dialog.useDialogControl()
const loadingDialogControl = Dialog.useDialogControl() const loadingDialogControl = Dialog.useDialogControl()
const record = starterPack.record as AppBskyGraphStarterpack.Record const record = starterPack.record as app.bsky.graph.starterpack.Main
const onPressCreate = () => { const onPressCreate = () => {
control.close(() => createDialogControl.open()) control.close(() => createDialogControl.open())
@@ -73,16 +74,19 @@ export function CreateListFromStarterPackDialog({
const listItems = await wait( const listItems = await wait(
3000, 3000,
(async () => { (async () => {
const items = await getAllListMembers(agent, starterPack.list!.uri) const items = await getAllListMembers(
appviewClient,
starterPack.list!.uri,
)
if (items.length > 0) { if (items.length > 0) {
const listitemWrites: $Typed<ComAtprotoRepoApplyWrites.Create>[] = const listitemWrites: $Typed<com.atproto.repo.applyWrites.Create>[] =
items.map(item => { items.map(item => {
const listitemRecord: $Typed<AppBskyGraphListitem.Record> = { const listitemRecord: $Typed<app.bsky.graph.listitem.Main> = {
$type: 'app.bsky.graph.listitem', $type: 'app.bsky.graph.listitem',
subject: item.subject.did, subject: item.subject.did,
list: listUri, list: listUri as AtUriString,
createdAt: new Date().toISOString(), createdAt: toDatetimeString(new Date()),
} }
return { return {
$type: 'com.atproto.repo.applyWrites#create', $type: 'com.atproto.repo.applyWrites#create',
@@ -94,8 +98,8 @@ export function CreateListFromStarterPackDialog({
const chunks = chunk(listitemWrites, 50) const chunks = chunk(listitemWrites, 50)
for (const c of chunks) { for (const c of chunks) {
await agent.com.atproto.repo.applyWrites({ await pdsClient.call(com.atproto.repo.applyWrites, {
repo: currentAccount.did, repo: currentAccount.did as AtIdentifierString,
writes: c, writes: c,
}) })
} }
@@ -103,10 +107,10 @@ export function CreateListFromStarterPackDialog({
await until( await until(
5, 5,
1e3, 1e3,
(res: {data: {items: unknown[]}}) => res.data.items.length > 0, (res: {items: unknown[]}) => res.items.length > 0,
() => () =>
agent.app.bsky.graph.getList({ appviewClient.call(app.bsky.graph.getList, {
list: listUri, list: listUri as AtUriString,
limit: 1, limit: 1,
}), }),
) )
@@ -1,6 +1,5 @@
import {useCallback, useEffect, useMemo, useState} from 'react' import {useCallback, useEffect, useMemo, useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyGraphDefs} from '@atproto/api'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -28,6 +27,7 @@ import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {type app} from '#/lexicons'
import {toLex} from '#/types/bsky' import {toLex} from '#/types/bsky'
const DISPLAY_NAME_MAX_GRAPHEMES = 64 const DISPLAY_NAME_MAX_GRAPHEMES = 64
@@ -47,8 +47,8 @@ export function CreateOrEditListDialog({
initialValues, initialValues,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
list?: AppBskyGraphDefs.ListView list?: app.bsky.graph.defs.ListView
purpose?: AppBskyGraphDefs.ListPurpose purpose?: app.bsky.graph.defs.ListPurpose
onSave?: (uri: string) => void onSave?: (uri: string) => void
initialValues?: InitialListValues initialValues?: InitialListValues
}) { }) {
@@ -115,8 +115,8 @@ function DialogInner({
onPressCancel, onPressCancel,
initialValues, initialValues,
}: { }: {
list?: AppBskyGraphDefs.ListView list?: app.bsky.graph.defs.ListView
purpose?: AppBskyGraphDefs.ListPurpose purpose?: app.bsky.graph.defs.ListPurpose
onSave?: (uri: string) => void onSave?: (uri: string) => void
setDirty: (dirty: boolean) => void setDirty: (dirty: boolean) => void
onPressCancel: () => void onPressCancel: () => void
@@ -1,6 +1,5 @@
import {useCallback, useMemo} from 'react' import {useCallback, useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyGraphDefs} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -23,6 +22,7 @@ import {
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {type app} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function ListAddRemoveUsersDialog({ export function ListAddRemoveUsersDialog({
@@ -31,7 +31,7 @@ export function ListAddRemoveUsersDialog({
onChange, onChange,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
list: AppBskyGraphDefs.ListView list: app.bsky.graph.defs.ListView
onChange?: ( onChange?: (
type: 'add' | 'remove', type: 'add' | 'remove',
profile: bsky.profile.AnyProfileView, profile: bsky.profile.AnyProfileView,
@@ -52,7 +52,7 @@ function DialogInner({
list, list,
onChange, onChange,
}: { }: {
list: AppBskyGraphDefs.ListView list: app.bsky.graph.defs.ListView
onChange?: ( onChange?: (
type: 'add' | 'remove', type: 'add' | 'remove',
profile: bsky.profile.AnyProfileView, profile: bsky.profile.AnyProfileView,
@@ -89,7 +89,7 @@ function DialogInner({
* Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record) * Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record)
*/ */
function getMembership( function getMembership(
listMembers: AppBskyGraphDefs.ListItemView[] | undefined, listMembers: app.bsky.graph.defs.ListItemView[] | undefined,
actorDid: string, actorDid: string,
): string | false | undefined { ): string | false | undefined {
if (!listMembers) { if (!listMembers) {
@@ -107,8 +107,8 @@ function UserResult({
moderationOpts, moderationOpts,
}: { }: {
profile: bsky.profile.AnyProfileView profile: bsky.profile.AnyProfileView
list: AppBskyGraphDefs.ListView list: app.bsky.graph.defs.ListView
listMembers: AppBskyGraphDefs.ListItemView[] | undefined listMembers: app.bsky.graph.defs.ListItemView[] | undefined
onChange?: ( onChange?: (
type: 'add' | 'remove', type: 'add' | 'remove',
profile: bsky.profile.AnyProfileView, profile: bsky.profile.AnyProfileView,
+2 -2
View File
@@ -6,7 +6,6 @@ import {
useMemo, useMemo,
useState, useState,
} from 'react' } from 'react'
import {type AppBskyActorDefs} from '@atproto/api'
import {logger} from '#/logger' import {logger} from '#/logger'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
@@ -30,6 +29,7 @@ import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils' import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {useGeolocation} from '#/geolocation' import {useGeolocation} from '#/geolocation'
import {type app} from '#/lexicons'
type Context = { type Context = {
activeNux: Nux | undefined activeNux: Nux | undefined
@@ -93,7 +93,7 @@ function Inner({
preferences, preferences,
}: { }: {
currentAccount: SessionAccount currentAccount: SessionAccount
currentProfile: AppBskyActorDefs.ProfileViewDetailed currentProfile: app.bsky.actor.defs.ProfileViewDetailed
preferences: UsePreferencesQueryResponse preferences: UsePreferencesQueryResponse
}) { }) {
const ax = useAnalytics() const ax = useAnalytics()
+2 -3
View File
@@ -1,14 +1,13 @@
import {type AppBskyActorDefs} from '@atproto/api'
import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' import {type UsePreferencesQueryResponse} from '#/state/queries/preferences'
import {type SessionAccount} from '#/state/session' import {type SessionAccount} from '#/state/session'
import {type AnalyticsContextType} from '#/analytics' import {type AnalyticsContextType} from '#/analytics'
import {type Geolocation} from '#/geolocation' import {type Geolocation} from '#/geolocation'
import {type app} from '#/lexicons'
export type EnabledCheckProps = { export type EnabledCheckProps = {
features: AnalyticsContextType['features'] features: AnalyticsContextType['features']
currentAccount: SessionAccount currentAccount: SessionAccount
currentProfile: AppBskyActorDefs.ProfileViewDetailed currentProfile: app.bsky.actor.defs.ProfileViewDetailed
preferences: UsePreferencesQueryResponse preferences: UsePreferencesQueryResponse
geolocation: Geolocation geolocation: Geolocation
} }
+2 -2
View File
@@ -1,11 +1,11 @@
import {View} from 'react-native' import {View} from 'react-native'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {MessageContextMenu} from '#/components/dms/MessageContextMenu' import {MessageContextMenu} from '#/components/dms/MessageContextMenu'
import {useMessageReplies} from '#/components/dms/MessageReplies' import {useMessageReplies} from '#/components/dms/MessageReplies'
import {SwipeToReply} from '#/components/dms/SwipeToReply' import {SwipeToReply} from '#/components/dms/SwipeToReply'
import {type chat} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function ActionsWrapper({ export function ActionsWrapper({
@@ -15,7 +15,7 @@ export function ActionsWrapper({
moderationOpts, moderationOpts,
children, children,
}: { }: {
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
isFromSelf: boolean isFromSelf: boolean
senderProfile?: bsky.profile.AnyProfileView senderProfile?: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts | undefined moderationOpts: ModerationOpts | undefined
+2 -2
View File
@@ -1,6 +1,5 @@
import {useCallback, useRef, useState} from 'react' import {useCallback, useRef, useState} from 'react'
import {Pressable, View} from 'react-native' import {Pressable, View} from 'react-native'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {plural} from '@lingui/core/macro' import {plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
@@ -14,6 +13,7 @@ import {MessageContextMenu} from '#/components/dms/MessageContextMenu'
import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid'
import {EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' import {EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {type chat} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {EmojiReactionPicker} from './EmojiReactionPicker' import {EmojiReactionPicker} from './EmojiReactionPicker'
import { import {
@@ -29,7 +29,7 @@ export function ActionsWrapper({
moderationOpts, moderationOpts,
children, children,
}: { }: {
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
isFromSelf: boolean isFromSelf: boolean
senderProfile?: bsky.profile.AnyProfileView senderProfile?: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts | undefined moderationOpts: ModerationOpts | undefined
@@ -1,6 +1,5 @@
import {memo, useState} from 'react' import {memo, useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native' import {StackActions, useNavigation} from '@react-navigation/native'
@@ -19,6 +18,7 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
type ReportDialogParams = { type ReportDialogParams = {
convoId: string convoId: string
@@ -113,7 +113,7 @@ function DoneStep({
}: { }: {
convoId: string convoId: string
currentScreen: 'list' | 'conversation' currentScreen: 'list' | 'conversation'
profile: AppBskyActorDefs.ProfileViewDetailed profile: app.bsky.actor.defs.ProfileViewDetailed
}) { }) {
const {t: l} = useLingui() const {t: l} = useLingui()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
+2 -2
View File
@@ -1,6 +1,5 @@
import {memo, useState} from 'react' import {memo, useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native' import {StackActions, useNavigation} from '@react-navigation/native'
@@ -19,6 +18,7 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
type ReportDialogParams = { type ReportDialogParams = {
convoId: string convoId: string
@@ -116,7 +116,7 @@ function DoneStep({
}: { }: {
convoId: string convoId: string
currentScreen: 'list' | 'conversation' currentScreen: 'list' | 'conversation'
profile: AppBskyActorDefs.ProfileViewDetailed profile: app.bsky.actor.defs.ProfileViewDetailed
}) { }) {
const {t: l} = useLingui() const {t: l} = useLingui()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
+4 -2
View File
@@ -1,5 +1,4 @@
import {View} from 'react-native' import {View} from 'react-native'
import {ChatBskyGroupDefs} from '@atproto/api'
import {Plural, Trans} from '@lingui/react/macro' import {Plural, Trans} from '@lingui/react/macro'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
@@ -10,6 +9,8 @@ import {AvatarBubbles} from '#/components/AvatarBubbles'
import {InlineLinkText} from '#/components/Link' import {InlineLinkText} from '#/components/Link'
import {ProfileBadges} from '#/components/ProfileBadges' import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {useChatInvite} from './Context' import {useChatInvite} from './Context'
/** /**
@@ -21,7 +22,8 @@ export function Card({size}: {size: 'large' | 'small'}) {
const t = useTheme() const t = useTheme()
const {preview, hasFixedHeight} = useChatInvite() const {preview, hasFixedHeight} = useChatInvite()
if (!ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) return null if (!bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview))
return null
const ownerDisplayName = createSanitizedDisplayName(preview.owner) const ownerDisplayName = createSanitizedDisplayName(preview.owner)
const ownerHandle = sanitizeHandle(preview.owner.handle, '@') const ownerHandle = sanitizeHandle(preview.owner.handle, '@')
+4 -3
View File
@@ -1,5 +1,4 @@
import {setStringAsync} from 'expo-clipboard' import {setStringAsync} from 'expo-clipboard'
import {ChatBskyGroupDefs} from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
@@ -18,6 +17,8 @@ import {type Props as SVGIconProps} from '#/components/icons/common'
import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand'
import {useIntentDialogs} from '#/components/intents/IntentDialogs' import {useIntentDialogs} from '#/components/intents/IntentDialogs'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import { import {
type ChatInviteAction, type ChatInviteAction,
ChatInviteProvider, ChatInviteProvider,
@@ -71,7 +72,7 @@ export function Root({
status = 'loading' status = 'loading'
} else if (error) { } else if (error) {
status = 'error' status = 'error'
} else if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { } else if (bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview)) {
status = 'available' status = 'available'
} else { } else {
// Resolved to a disabled/invalid/unrecognized preview - nothing to join. // Resolved to a disabled/invalid/unrecognized preview - nothing to join.
@@ -79,7 +80,7 @@ export function Root({
} }
let action: ChatInviteAction | undefined let action: ChatInviteAction | undefined
if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { if (bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview)) {
const convoId = preview.convo?.id const convoId = preview.convo?.id
const isFollowing = preview.owner.viewer?.followedBy ?? false const isFollowing = preview.owner.viewer?.followedBy ?? false
const hasRequested = !convoId && preview.viewer?.requestedAt != null const hasRequested = !convoId && preview.viewer?.requestedAt != null
+2 -2
View File
@@ -1,6 +1,5 @@
import {useMemo, useState} from 'react' import {useMemo, useState} from 'react'
import {useWindowDimensions, View} from 'react-native' import {useWindowDimensions, View} from 'react-native'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -14,6 +13,7 @@ import {
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import {type TriggerProps} from '#/components/Menu/types' import {type TriggerProps} from '#/components/Menu/types'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type chat} from '#/lexicons'
import {EmojiPopup} from './EmojiPopup' import {EmojiPopup} from './EmojiPopup'
import {hasAlreadyReacted, hasReachedReactionLimit} from './util' import {hasAlreadyReacted, hasReachedReactionLimit} from './util'
@@ -21,7 +21,7 @@ export function EmojiReactionPicker({
message, message,
onEmojiSelect, onEmojiSelect,
}: { }: {
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
children?: TriggerProps['children'] children?: TriggerProps['children']
onEmojiSelect: (emoji: string) => void onEmojiSelect: (emoji: string) => void
}) { }) {
@@ -1,6 +1,5 @@
import {useState} from 'react' import {useState} from 'react'
import {Pressable, View} from 'react-native' import {Pressable, View} from 'react-native'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {DropdownMenu} from 'radix-ui' import {DropdownMenu} from 'radix-ui'
@@ -10,6 +9,7 @@ import * as EmojiPicker from '#/components/EmojiPicker'
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type chat} from '#/lexicons'
import {hasAlreadyReacted, hasReachedReactionLimit} from './util' import {hasAlreadyReacted, hasReachedReactionLimit} from './util'
export function EmojiReactionPicker({ export function EmojiReactionPicker({
@@ -17,7 +17,7 @@ export function EmojiReactionPicker({
children, children,
onEmojiSelect, onEmojiSelect,
}: { }: {
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
children?: EmojiPicker.TriggerProps['children'] children?: EmojiPicker.TriggerProps['children']
onEmojiSelect: (emoji: string) => void onEmojiSelect: (emoji: string) => void
}) { }) {
@@ -40,7 +40,7 @@ function MenuInner({
message, message,
onEmojiSelect, onEmojiSelect,
}: { }: {
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
onEmojiSelect: (emoji: string) => void onEmojiSelect: (emoji: string) => void
}) { }) {
const t = useTheme() const t = useTheme()
+3 -5
View File
@@ -1,9 +1,9 @@
import {ChatBskyConvoLeaveConvo} from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native' import {StackActions, useNavigation} from '@react-navigation/native'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {isNetworkError} from '#/lib/strings/errors' import {isNetworkError} from '#/lib/strings/errors'
import {getErrorName} from '#/lib/xrpc-error'
import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
import {type DialogOuterProps} from '#/components/Dialog' import {type DialogOuterProps} from '#/components/Dialog'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
@@ -36,11 +36,9 @@ export function LeaveConvoPrompt({
let errorMessage = l`Could not leave chat` let errorMessage = l`Could not leave chat`
if (isNetworkError(error)) { if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.` errorMessage = l`A network error occurred. Please check your internet connection.`
} else if (error instanceof ChatBskyConvoLeaveConvo.InvalidConvoError) { } else if (getErrorName(error) === 'InvalidConvo') {
errorMessage = l`Conversation not found.` errorMessage = l`Conversation not found.`
} else if ( } else if (getErrorName(error) === 'OwnerCannotLeave') {
error instanceof ChatBskyConvoLeaveConvo.OwnerCannotLeaveError
) {
errorMessage = l`Owner must lock the group before leaving.` errorMessage = l`Owner must lock the group before leaving.`
} }
Toast.show(errorMessage, {type: 'error'}) Toast.show(errorMessage, {type: 'error'})
+2 -2
View File
@@ -2,7 +2,6 @@ import {memo, useCallback} from 'react'
import {Platform} from 'react-native' import {Platform} from 'react-native'
import {type GestureType} from 'react-native-gesture-handler' import {type GestureType} from 'react-native-gesture-handler'
import * as Clipboard from 'expo-clipboard' import * as Clipboard from 'expo-clipboard'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {RichText} from '@bsky.app/sdk/richtext' import {RichText} from '@bsky.app/sdk/richtext'
import {plural} from '@lingui/core/macro' import {plural} from '@lingui/core/macro'
@@ -28,6 +27,7 @@ import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Tra
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type chat} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky' import {toLex} from '#/types/bsky'
import {EmojiReactionPicker} from './EmojiReactionPicker' import {EmojiReactionPicker} from './EmojiReactionPicker'
@@ -40,7 +40,7 @@ export let MessageContextMenu = ({
children, children,
swipeGesture, swipeGesture,
}: { }: {
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
senderProfile?: bsky.profile.AnyProfileView senderProfile?: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts | undefined moderationOpts: ModerationOpts | undefined
children: TriggerProps['children'] children: TriggerProps['children']
+56 -43
View File
@@ -22,12 +22,6 @@ import Animated, {
ZoomIn, ZoomIn,
ZoomOut, ZoomOut,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {
AppBskyEmbedRecord,
type ChatBskyActorDefs,
ChatBskyConvoDefs,
ChatBskyEmbedJoinLink,
} from '@atproto/api'
import {moderateProfile} from '@bsky.app/sdk/moderation' import {moderateProfile} from '@bsky.app/sdk/moderation'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {plural} from '@lingui/core/macro' import {plural} from '@lingui/core/macro'
@@ -58,7 +52,8 @@ import * as ProfileCard from '#/components/ProfileCard'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {toLex} from '#/types/bsky' import {app, chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {DateDivider} from './DateDivider' import {DateDivider} from './DateDivider'
import {MessageItemEmbed} from './MessageItemEmbed' import {MessageItemEmbed} from './MessageItemEmbed'
import {MessageItemInviteEmbed} from './MessageItemInviteEmbed' import {MessageItemInviteEmbed} from './MessageItemInviteEmbed'
@@ -77,16 +72,19 @@ const SQUARED_BORDER_RADIUS = 4
const DISPLAY_NAME_INSET = 20 const DISPLAY_NAME_INSET = 20
export type MessageItemNeighbor = export type MessageItemNeighbor =
| ChatBskyConvoDefs.MessageView | chat.bsky.convo.defs.MessageView
| ChatBskyConvoDefs.DeletedMessageView | chat.bsky.convo.defs.DeletedMessageView
| null | null
function messageIsReply(message: MessageItemNeighbor): boolean { function messageIsReply(message: MessageItemNeighbor): boolean {
return ( return (
ChatBskyConvoDefs.isMessageView(message) && bsky.isType(chat.bsky.convo.defs.messageView, message) &&
(ChatBskyConvoDefs.isMessageView(message.replyTo) || (bsky.isType(chat.bsky.convo.defs.messageView, message.replyTo) ||
ChatBskyConvoDefs.isDeletedMessageView(message.replyTo) || bsky.isType(chat.bsky.convo.defs.deletedMessageView, message.replyTo) ||
ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(message.replyTo)) bsky.isType(
chat.bsky.convo.defs.messageBeforeUserJoinedGroupView,
message.replyTo,
))
) )
} }
@@ -98,7 +96,7 @@ function isWithinClusterBoundary({
direction, direction,
}: { }: {
isPending: boolean isPending: boolean
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
adjacentMessage: MessageItemNeighbor adjacentMessage: MessageItemNeighbor
isFromSameSender: boolean isFromSameSender: boolean
direction: 'prev' | 'next' direction: 'prev' | 'next'
@@ -110,7 +108,7 @@ function isWithinClusterBoundary({
return true return true
} }
if (!isFromSameSender) return true if (!isFromSameSender) return true
if (ChatBskyConvoDefs.isMessageView(adjacentMessage)) { if (bsky.isType(chat.bsky.convo.defs.messageView, adjacentMessage)) {
const currentSentAt = message.sentAt const currentSentAt = message.sentAt
const thisDate = new Date(currentSentAt) const thisDate = new Date(currentSentAt)
const adjDate = new Date(adjacentMessage.sentAt) const adjDate = new Date(adjacentMessage.sentAt)
@@ -137,7 +135,7 @@ let MessageItem = ({
isGroupChat?: boolean isGroupChat?: boolean
prevMessage: MessageItemNeighbor prevMessage: MessageItemNeighbor
nextMessage: MessageItemNeighbor nextMessage: MessageItemNeighbor
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic> relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
}): React.ReactNode => { }): React.ReactNode => {
const t = useTheme() const t = useTheme()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -155,13 +153,17 @@ let MessageItem = ({
// tombstone, or a before-joined placeholder. Narrow away the open-union // tombstone, or a before-joined placeholder. Narrow away the open-union
// fallback so we only render shapes we understand. // fallback so we only render shapes we understand.
const replyTo = const replyTo =
ChatBskyConvoDefs.isMessageView(message.replyTo) || bsky.isType(chat.bsky.convo.defs.messageView, message.replyTo) ||
ChatBskyConvoDefs.isDeletedMessageView(message.replyTo) || bsky.isType(chat.bsky.convo.defs.deletedMessageView, message.replyTo) ||
ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(message.replyTo) bsky.isType(
chat.bsky.convo.defs.messageBeforeUserJoinedGroupView,
message.replyTo,
)
? message.replyTo ? message.replyTo
: undefined : undefined
const replyToMessageId = const replyToMessageId =
replyTo && !ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(replyTo) replyTo &&
!bsky.isType(chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, replyTo)
? replyTo.id ? replyTo.id
: undefined : undefined
const onPressReplyTo = replyToMessageId const onPressReplyTo = replyToMessageId
@@ -175,8 +177,14 @@ let MessageItem = ({
const isFromSelf = const isFromSelf =
message.sender?.did != null && message.sender.did === currentAccount?.did message.sender?.did != null && message.sender.did === currentAccount?.did
const prevIsMessage = ChatBskyConvoDefs.isMessageView(prevMessage) const prevIsMessage = bsky.isType(
const nextIsMessage = ChatBskyConvoDefs.isMessageView(nextMessage) chat.bsky.convo.defs.messageView,
prevMessage,
)
const nextIsMessage = bsky.isType(
chat.bsky.convo.defs.messageView,
nextMessage,
)
const isPrevFromSameSender = const isPrevFromSameSender =
prevIsMessage && prevIsMessage &&
@@ -204,7 +212,7 @@ let MessageItem = ({
}) })
const hasLargeGapFromPrev = const hasLargeGapFromPrev =
!ChatBskyConvoDefs.isMessageView(prevMessage) || !bsky.isType(chat.bsky.convo.defs.messageView, prevMessage) ||
new Date(message.sentAt).getTime() - new Date(message.sentAt).getTime() -
new Date(prevMessage.sentAt).getTime() > new Date(prevMessage.sentAt).getTime() >
MESSAGE_GAP_THRESHOLD_MS MESSAGE_GAP_THRESHOLD_MS
@@ -251,8 +259,8 @@ let MessageItem = ({
const isEmojiOnly = isOnlyEmoji(message.text) const isEmojiOnly = isOnlyEmoji(message.text)
const hasEmbed = const hasEmbed =
AppBskyEmbedRecord.isView(message.embed) || bsky.isType(app.bsky.embed.record.view, message.embed) ||
ChatBskyEmbedJoinLink.isView(message.embed) bsky.isType(chat.bsky.embed.joinLink.view, message.embed)
const hasEmbedAndText = hasEmbed && rt.text.length > 0 const hasEmbedAndText = hasEmbed && rt.text.length > 0
const targetBottomRadius = squaredBottomCorner const targetBottomRadius = squaredBottomCorner
@@ -336,7 +344,7 @@ let MessageItem = ({
size={AVATAR_SIZE} size={AVATAR_SIZE}
type={profile.associated?.labeler ? 'labeler' : 'user'} type={profile.associated?.labeler ? 'labeler' : 'user'}
onBeforePress={() => unstableCacheProfileView(queryClient, profile)} onBeforePress={() => unstableCacheProfileView(queryClient, profile)}
moderation={moderateProfile(toLex(profile), moderationOpts).ui( moderation={moderateProfile(bsky.toLex(profile), moderationOpts).ui(
'avatar', 'avatar',
)} )}
/> />
@@ -515,7 +523,7 @@ let MessageItem = ({
message={message} message={message}
senderProfile={profile} senderProfile={profile}
moderationOpts={moderationOpts}> moderationOpts={moderationOpts}>
{AppBskyEmbedRecord.isView(message.embed) && ( {bsky.isType(app.bsky.embed.record.view, message.embed) && (
<MessageItemEmbed <MessageItemEmbed
embed={message.embed} embed={message.embed}
isFromSelf={isFromSelf} isFromSelf={isFromSelf}
@@ -527,7 +535,10 @@ let MessageItem = ({
highlightSV={highlightSV} highlightSV={highlightSV}
/> />
)} )}
{ChatBskyEmbedJoinLink.isView(message.embed) && ( {bsky.isType(
chat.bsky.embed.joinLink.view,
message.embed,
) && (
<MessageItemInviteEmbed <MessageItemInviteEmbed
embed={message.embed} embed={message.embed}
isFromSelf={isFromSelf} isFromSelf={isFromSelf}
@@ -665,7 +676,7 @@ function BlockedPlaceholder({
profile, profile,
style, style,
}: { }: {
profile: Shadow<ChatBskyActorDefs.ProfileViewBasic> profile: Shadow<chat.bsky.actor.defs.ProfileViewBasic>
style?: AnimatedStyle<ViewStyle> style?: AnimatedStyle<ViewStyle>
}) { }) {
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -775,13 +786,13 @@ function ReplyCaption({
onPress, onPress,
}: { }: {
replyTo: replyTo:
| ChatBskyConvoDefs.MessageView | chat.bsky.convo.defs.MessageView
| ChatBskyConvoDefs.DeletedMessageView | chat.bsky.convo.defs.DeletedMessageView
| ChatBskyConvoDefs.MessageBeforeUserJoinedGroupView | chat.bsky.convo.defs.MessageBeforeUserJoinedGroupView
isFromSelf: boolean isFromSelf: boolean
isGroupChat: boolean isGroupChat: boolean
replierDisplayName: string | null replierDisplayName: string | null
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic> relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
onPress?: () => void onPress?: () => void
}) { }) {
const t = useTheme() const t = useTheme()
@@ -790,8 +801,8 @@ function ReplyCaption({
let caption: string = '' let caption: string = ''
if ( if (
ChatBskyConvoDefs.isMessageView(replyTo) || bsky.isType(chat.bsky.convo.defs.messageView, replyTo) ||
ChatBskyConvoDefs.isDeletedMessageView(replyTo) bsky.isType(chat.bsky.convo.defs.deletedMessageView, replyTo)
) { ) {
const originalSenderIsSelf = replyTo.sender.did === currentAccount?.did const originalSenderIsSelf = replyTo.sender.did === currentAccount?.did
const originalProfile = relatedProfiles.get(replyTo.sender.did) const originalProfile = relatedProfiles.get(replyTo.sender.did)
@@ -864,11 +875,11 @@ function ReplyQuote({
onPress, onPress,
}: { }: {
replyTo: replyTo:
| ChatBskyConvoDefs.MessageView | chat.bsky.convo.defs.MessageView
| ChatBskyConvoDefs.DeletedMessageView | chat.bsky.convo.defs.DeletedMessageView
| ChatBskyConvoDefs.MessageBeforeUserJoinedGroupView | chat.bsky.convo.defs.MessageBeforeUserJoinedGroupView
isFromSelf: boolean isFromSelf: boolean
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic> relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
onPress?: () => void onPress?: () => void
}) { }) {
const t = useTheme() const t = useTheme()
@@ -876,8 +887,8 @@ function ReplyQuote({
const getReplyPreviewText = useReplyPreviewText() const getReplyPreviewText = useReplyPreviewText()
const senderDid = const senderDid =
ChatBskyConvoDefs.isMessageView(replyTo) || bsky.isType(chat.bsky.convo.defs.messageView, replyTo) ||
ChatBskyConvoDefs.isDeletedMessageView(replyTo) bsky.isType(chat.bsky.convo.defs.deletedMessageView, replyTo)
? replyTo.sender.did ? replyTo.sender.did
: undefined : undefined
const senderProfile = useMaybeProfileShadow( const senderProfile = useMaybeProfileShadow(
@@ -907,9 +918,11 @@ function ReplyQuote({
comment: 'A reply summary in chat', comment: 'A reply summary in chat',
}) })
subtle = true subtle = true
} else if (ChatBskyConvoDefs.isMessageView(replyTo)) { } else if (bsky.isType(chat.bsky.convo.defs.messageView, replyTo)) {
;({text, subtle} = getReplyPreviewText(replyTo)) ;({text, subtle} = getReplyPreviewText(replyTo))
} else if (ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(replyTo)) { } else if (
bsky.isType(chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, replyTo)
) {
text = l({ text = l({
message: `(message sent before you joined)`, message: `(message sent before you joined)`,
comment: 'A reply summary in chat', comment: 'A reply summary in chat',
+3 -2
View File
@@ -5,10 +5,11 @@ import Animated, {
type SharedValue, type SharedValue,
useAnimatedStyle, useAnimatedStyle,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {type $Typed, type AppBskyEmbedRecord} from '@atproto/api' import {type $Typed} from '@atproto/lex'
import {atoms as a, native, useTheme, web} from '#/alf' import {atoms as a, native, useTheme, web} from '#/alf'
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
import {type app} from '#/lexicons'
import {MessageContextProvider} from './MessageContext' import {MessageContextProvider} from './MessageContext'
const BORDER_RADIUS = 20 const BORDER_RADIUS = 20
@@ -22,7 +23,7 @@ let MessageItemEmbed = ({
squaredBottomCorner, squaredBottomCorner,
highlightSV, highlightSV,
}: { }: {
embed: $Typed<AppBskyEmbedRecord.View> embed: $Typed<app.bsky.embed.record.View>
isFromSelf: boolean isFromSelf: boolean
isGroupChat: boolean isGroupChat: boolean
squaredTopCorner: boolean squaredTopCorner: boolean
@@ -5,12 +5,13 @@ import Animated, {
type SharedValue, type SharedValue,
useAnimatedStyle, useAnimatedStyle,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {type $Typed, type ChatBskyEmbedJoinLink} from '@atproto/api' import {type $Typed} from '@atproto/lex'
import {useConvoActive} from '#/state/messages/convo' import {useConvoActive} from '#/state/messages/convo'
import {isKnownJoinLinkPreview} from '#/state/queries/join-links' import {isKnownJoinLinkPreview} from '#/state/queries/join-links'
import {atoms as a, native, useTheme, web} from '#/alf' import {atoms as a, native, useTheme, web} from '#/alf'
import * as ChatInvite from '#/components/dms/ChatInvite' import * as ChatInvite from '#/components/dms/ChatInvite'
import {type chat} from '#/lexicons'
import {MessageContextProvider} from './MessageContext' import {MessageContextProvider} from './MessageContext'
const BORDER_RADIUS = 20 const BORDER_RADIUS = 20
@@ -24,7 +25,7 @@ let MessageItemInviteEmbed = ({
squaredBottomCorner, squaredBottomCorner,
highlightSV, highlightSV,
}: { }: {
embed: $Typed<ChatBskyEmbedJoinLink.View> embed: $Typed<chat.bsky.embed.joinLink.View>
isFromSelf: boolean isFromSelf: boolean
isGroupChat: boolean isGroupChat: boolean
squaredTopCorner: boolean squaredTopCorner: boolean
+11 -11
View File
@@ -7,7 +7,6 @@ import {
useState, useState,
} from 'react' } from 'react'
import {LayoutAnimation} from 'react-native' import {LayoutAnimation} from 'react-native'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
@@ -20,15 +19,16 @@ import {ReportDialog} from '#/components/moderation/ReportDialog'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {usePromptControl} from '#/components/Prompt' import {usePromptControl} from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {type chat} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
type MessageDialogsContextType = { type MessageDialogsContextType = {
openDeleteMessage: (message: ChatBskyConvoDefs.MessageView) => void openDeleteMessage: (message: chat.bsky.convo.defs.MessageView) => void
openReportMessage: ( openReportMessage: (
message: ChatBskyConvoDefs.MessageView, message: chat.bsky.convo.defs.MessageView,
senderProfile: bsky.profile.AnyProfileView | undefined, senderProfile: bsky.profile.AnyProfileView | undefined,
) => void ) => void
openReactions: (message: ChatBskyConvoDefs.MessageView) => void openReactions: (message: chat.bsky.convo.defs.MessageView) => void
} }
const Context = createContext<MessageDialogsContextType | null>(null) const Context = createContext<MessageDialogsContextType | null>(null)
@@ -52,18 +52,18 @@ export function MessageOverlays({children}: {children: React.ReactNode}) {
const reactionsControl = useDialogControl() const reactionsControl = useDialogControl()
const [deleteTarget, setDeleteTarget] = const [deleteTarget, setDeleteTarget] =
useState<ChatBskyConvoDefs.MessageView | null>(null) useState<chat.bsky.convo.defs.MessageView | null>(null)
const [reportTarget, setReportTarget] = useState<{ const [reportTarget, setReportTarget] = useState<{
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
senderProfile: bsky.profile.AnyProfileView | undefined senderProfile: bsky.profile.AnyProfileView | undefined
} | null>(null) } | null>(null)
const [afterReportTarget, setAfterReportTarget] = const [afterReportTarget, setAfterReportTarget] =
useState<ChatBskyConvoDefs.MessageView | null>(null) useState<chat.bsky.convo.defs.MessageView | null>(null)
const [reactionsTarget, setReactionsTarget] = const [reactionsTarget, setReactionsTarget] =
useState<ChatBskyConvoDefs.MessageView | null>(null) useState<chat.bsky.convo.defs.MessageView | null>(null)
const openDeleteMessage = useCallback( const openDeleteMessage = useCallback(
(message: ChatBskyConvoDefs.MessageView) => { (message: chat.bsky.convo.defs.MessageView) => {
setDeleteTarget(message) setDeleteTarget(message)
deleteControl.open() deleteControl.open()
}, },
@@ -72,7 +72,7 @@ export function MessageOverlays({children}: {children: React.ReactNode}) {
const openReportMessage = useCallback( const openReportMessage = useCallback(
( (
message: ChatBskyConvoDefs.MessageView, message: chat.bsky.convo.defs.MessageView,
senderProfile: bsky.profile.AnyProfileView | undefined, senderProfile: bsky.profile.AnyProfileView | undefined,
) => { ) => {
setReportTarget({message, senderProfile}) setReportTarget({message, senderProfile})
@@ -82,7 +82,7 @@ export function MessageOverlays({children}: {children: React.ReactNode}) {
) )
const openReactions = useCallback( const openReactions = useCallback(
(message: ChatBskyConvoDefs.MessageView) => { (message: chat.bsky.convo.defs.MessageView) => {
setReactionsTarget(message) setReactionsTarget(message)
}, },
[], [],
+2 -2
View File
@@ -1,6 +1,5 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -16,11 +15,12 @@ import {canBeMessaged} from '#/components/dms/util'
import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message' import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type app} from '#/lexicons'
export function MessageProfileButton({ export function MessageProfileButton({
profile, profile,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed profile: app.bsky.actor.defs.ProfileViewDetailed
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
+7 -7
View File
@@ -7,7 +7,8 @@ import {
useRef, useRef,
useState, useState,
} from 'react' } from 'react'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {type chat} from '#/lexicons'
/** /**
* How long a message stays highlighted after scrolling to it, before the flash * How long a message stays highlighted after scrolling to it, before the flash
@@ -28,8 +29,8 @@ type MessageRepliesContextType = {
/** /**
* The message currently staged for reply in the composer, or null. * The message currently staged for reply in the composer, or null.
*/ */
replyTo: ChatBskyConvoDefs.MessageView | null replyTo: chat.bsky.convo.defs.MessageView | null
setReply: (message: ChatBskyConvoDefs.MessageView) => void setReply: (message: chat.bsky.convo.defs.MessageView) => void
clearReply: () => void clearReply: () => void
/** /**
* Scroll the list to a message, if it's currently loaded, and flash it. No-op * Scroll the list to a message, if it's currently loaded, and flash it. No-op
@@ -66,9 +67,8 @@ export function MessageRepliesProvider({
*/ */
scrollToMessage: (messageId: string) => boolean scrollToMessage: (messageId: string) => boolean
}) { }) {
const [replyTo, setReplyTo] = useState<ChatBskyConvoDefs.MessageView | null>( const [replyTo, setReplyTo] =
null, useState<chat.bsky.convo.defs.MessageView | null>(null)
)
const [highlightedMessage, setHighlightedMessage] = const [highlightedMessage, setHighlightedMessage] =
useState<HighlightedMessage | null>(null) useState<HighlightedMessage | null>(null)
const highlightKey = useRef(0) const highlightKey = useRef(0)
@@ -76,7 +76,7 @@ export function MessageRepliesProvider({
null, null,
) )
const setReply = useCallback((message: ChatBskyConvoDefs.MessageView) => { const setReply = useCallback((message: chat.bsky.convo.defs.MessageView) => {
setReplyTo(message) setReplyTo(message)
}, []) }, [])
+8 -8
View File
@@ -6,7 +6,6 @@ import {
useWindowDimensions, useWindowDimensions,
View, View,
} from 'react-native' } from 'react-native'
import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {HITSLOP_10} from '#/lib/constants' import {HITSLOP_10} from '#/lib/constants'
@@ -23,12 +22,13 @@ import {filterBlockedReactions} from '#/components/dms/util'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import {type chat} from '#/lexicons'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
type Reaction = { type Reaction = {
key: string key: string
value: string value: string
senders: ChatBskyConvoDefs.ReactionViewSender[] senders: chat.bsky.convo.defs.ReactionViewSender[]
count: number count: number
} }
@@ -41,8 +41,8 @@ export function ReactionsDialog({
onClose, onClose,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic> relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
onClose?: () => void onClose?: () => void
}) { }) {
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -142,10 +142,10 @@ function ReactionRow({
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
convo: ActiveConvoStates convo: ActiveConvoStates
currentAccount?: SessionAccount currentAccount?: SessionAccount
message: ChatBskyConvoDefs.MessageView message: chat.bsky.convo.defs.MessageView
profile: bsky.profile.AnyProfileView profile: bsky.profile.AnyProfileView
reaction: ChatBskyConvoDefs.ReactionView reaction: chat.bsky.convo.defs.ReactionView
allReactions: ChatBskyConvoDefs.ReactionView[] allReactions: chat.bsky.convo.defs.ReactionView[]
selected: string selected: string
setSelected: React.Dispatch<React.SetStateAction<string>> setSelected: React.Dispatch<React.SetStateAction<string>>
}) { }) {
@@ -398,7 +398,7 @@ function ReactionTab({
} }
export function groupReactions( export function groupReactions(
reactions: ChatBskyConvoDefs.ReactionView[] | undefined, reactions: chat.bsky.convo.defs.ReactionView[] | undefined,
): Reaction[] { ): Reaction[] {
const grouped = new Map<string, Reaction>() const grouped = new Map<string, Reaction>()
for (const reaction of reactions ?? []) { for (const reaction of reactions ?? []) {
+2 -2
View File
@@ -7,7 +7,6 @@ import Animated, {
useDerivedValue, useDerivedValue,
withTiming, withTiming,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {type ChatBskyActorDefs} from '@atproto/api'
import {plural} from '@lingui/core/macro' import {plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
@@ -17,6 +16,7 @@ import {atoms as a, useTheme} from '#/alf'
import {SystemMessageItem} from '#/components/dms/SystemMessageItem' import {SystemMessageItem} from '#/components/dms/SystemMessageItem'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type chat} from '#/lexicons'
const ANIMATION_DURATION_MS = 200 const ANIMATION_DURATION_MS = 200
@@ -29,7 +29,7 @@ export function SystemMessageGroup({
item: SystemMessageGroupItem item: SystemMessageGroupItem
expanded: boolean expanded: boolean
onToggle: (key: string) => void onToggle: (key: string) => void
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic> relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
+2 -2
View File
@@ -1,5 +1,4 @@
import {View} from 'react-native' import {View} from 'react-native'
import {type ChatBskyActorDefs} from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
@@ -10,13 +9,14 @@ import {Button} from '#/components/Button'
import {getSystemMessageInfo} from '#/components/dms/getSystemMessageInfo' import {getSystemMessageInfo} from '#/components/dms/getSystemMessageInfo'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type chat} from '#/lexicons'
export function SystemMessageItem({ export function SystemMessageItem({
item, item,
relatedProfiles, relatedProfiles,
}: { }: {
item: ConvoItem & {type: 'system-message'} item: ConvoItem & {type: 'system-message'}
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic> relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>
}) { }) {
const t = useTheme() const t = useTheme()
const {i18n, t: l} = useLingui() const {i18n, t: l} = useLingui()
+12 -37
View File
@@ -1,12 +1,9 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {
ChatBskyConvoGetConvoForMembers,
ChatBskyGroupCreateGroup,
} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
import {isNetworkError} from '#/lib/strings/errors' import {isNetworkError} from '#/lib/strings/errors'
import {getErrorName} from '#/lib/xrpc-error'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat' import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat'
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
@@ -54,26 +51,15 @@ export function NewChat({
let errorMessage = l`An issue occurred starting the chat, please try again.` let errorMessage = l`An issue occurred starting the chat, please try again.`
if (isNetworkError(error)) { if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.` errorMessage = l`A network error occurred. Please check your internet connection.`
} else if ( } else if (getErrorName(error) === 'AccountSuspended') {
error instanceof ChatBskyConvoGetConvoForMembers.AccountSuspendedError
) {
errorMessage = l`Suspended accounts cannot participate in chat.` errorMessage = l`Suspended accounts cannot participate in chat.`
} else if ( } else if (getErrorName(error) === 'BlockedActor') {
error instanceof ChatBskyConvoGetConvoForMembers.BlockedActorError
) {
errorMessage = l`This user has blocked you and cannot be messaged.` errorMessage = l`This user has blocked you and cannot be messaged.`
} else if ( } else if (getErrorName(error) === 'MessagesDisabled') {
error instanceof ChatBskyConvoGetConvoForMembers.MessagesDisabledError
) {
errorMessage = l`This user has disabled chat and cannot be messaged.` errorMessage = l`This user has disabled chat and cannot be messaged.`
} else if ( } else if (getErrorName(error) === 'NotFollowedBySender') {
error instanceof
ChatBskyConvoGetConvoForMembers.NotFollowedBySenderError
) {
errorMessage = l`Chat recipient is not followed by the sender.` errorMessage = l`Chat recipient is not followed by the sender.`
} else if ( } else if (getErrorName(error) === 'RecipientNotFound') {
error instanceof ChatBskyConvoGetConvoForMembers.RecipientNotFoundError
) {
errorMessage = l`Unable to find the selected recipient.` errorMessage = l`Unable to find the selected recipient.`
} }
Toast.show(errorMessage, { Toast.show(errorMessage, {
@@ -92,28 +78,17 @@ export function NewChat({
let errorMessage = l`An issue occurred starting the group chat, please try again.` let errorMessage = l`An issue occurred starting the group chat, please try again.`
if (isNetworkError(error)) { if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.` errorMessage = l`A network error occurred. Please check your internet connection.`
} else if ( } else if (getErrorName(error) === 'AccountSuspended') {
error instanceof ChatBskyGroupCreateGroup.AccountSuspendedError
) {
errorMessage = l`Suspended accounts cannot participate in a group chat.` errorMessage = l`Suspended accounts cannot participate in a group chat.`
} else if (error instanceof ChatBskyGroupCreateGroup.BlockedActorError) { } else if (getErrorName(error) === 'BlockedActor') {
errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.` errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.`
} else if ( } else if (getErrorName(error) === 'NewAccountCannotCreateGroup') {
error instanceof
ChatBskyGroupCreateGroup.NewAccountCannotCreateGroupError
) {
errorMessage = l`You cannot create a group chat yet.` errorMessage = l`You cannot create a group chat yet.`
} else if ( } else if (getErrorName(error) === 'NotFollowedBySender') {
error instanceof ChatBskyGroupCreateGroup.NotFollowedBySenderError
) {
errorMessage = l`A selected recipient is not followed by the sender.` errorMessage = l`A selected recipient is not followed by the sender.`
} else if ( } else if (getErrorName(error) === 'RecipientNotFound') {
error instanceof ChatBskyGroupCreateGroup.RecipientNotFoundError
) {
errorMessage = l`Unable to find a selected recipient.` errorMessage = l`Unable to find a selected recipient.`
} else if ( } else if (getErrorName(error) === 'UserForbidsGroups') {
error instanceof ChatBskyGroupCreateGroup.UserForbidsGroupsError
) {
errorMessage = l`One of the selected recipients does not allow group chats.` errorMessage = l`One of the selected recipients does not allow group chats.`
} }
Toast.show(errorMessage, { Toast.show(errorMessage, {
+10 -15
View File
@@ -1,9 +1,3 @@
import {
AppBskyEmbedRecord,
type ChatBskyActorDefs,
ChatBskyConvoDefs,
ChatBskyEmbedJoinLink,
} from '@atproto/api'
import {type I18n} from '@lingui/core' import {type I18n} from '@lingui/core'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
@@ -14,12 +8,13 @@ import {
toBskyAppUrl, toBskyAppUrl,
toShortUrl, toShortUrl,
} from '#/lib/strings/url-helpers' } from '#/lib/strings/url-helpers'
import type * as bsky from '#/types/bsky' import {app, chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
export type UserMessageInfo = { export type UserMessageInfo = {
message: string | null message: string | null
sentAt: string sentAt: string
reportableMessage?: ChatBskyConvoDefs.MessageView reportableMessage?: chat.bsky.convo.defs.MessageView
isBlockedMessage: boolean isBlockedMessage: boolean
} }
@@ -36,7 +31,7 @@ export function isDidBlockedInConvo({
primaryProfile, primaryProfile,
}: { }: {
did: string | undefined did: string | undefined
members: ChatBskyActorDefs.ProfileViewBasic[] members: chat.bsky.actor.defs.ProfileViewBasic[]
primaryProfile?: bsky.profile.AnyProfileView primaryProfile?: bsky.profile.AnyProfileView
}): boolean { }): boolean {
if (!did) return false if (!did) return false
@@ -53,12 +48,12 @@ export function getMessageInfo({
primaryProfile, primaryProfile,
i18n, i18n,
}: { }: {
convo: ChatBskyConvoDefs.ConvoView convo: chat.bsky.convo.defs.ConvoView
currentAccountDid: string | undefined currentAccountDid: string | undefined
primaryProfile?: bsky.profile.AnyProfileView primaryProfile?: bsky.profile.AnyProfileView
i18n: I18n i18n: I18n
}): UserMessageInfo | null { }): UserMessageInfo | null {
if (!ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { if (!bsky.isType(chat.bsky.convo.defs.messageView, convo.lastMessage)) {
return null return null
} }
@@ -67,7 +62,7 @@ export function getMessageInfo({
const senderDid = lastMessage.sender?.did const senderDid = lastMessage.sender?.did
const sender = convo.members.find(m => m.did === senderDid) const sender = convo.members.find(m => m.did === senderDid)
const name = sender ? createSanitizedDisplayName(sender) : null const name = sender ? createSanitizedDisplayName(sender) : null
const isGroup = ChatBskyConvoDefs.isGroupConvo(convo.kind) const isGroup = bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)
const reportableMessage = isFromMe ? undefined : lastMessage const reportableMessage = isFromMe ? undefined : lastMessage
const isBlockedMessage = isDidBlockedInConvo({ const isBlockedMessage = isDidBlockedInConvo({
@@ -105,10 +100,10 @@ export function getMessageInfo({
msg`(contains embedded content)`, msg`(contains embedded content)`,
) )
if (AppBskyEmbedRecord.isView(lastMessage.embed)) { if (bsky.isType(app.bsky.embed.record.view, lastMessage.embed)) {
const embed = lastMessage.embed const embed = lastMessage.embed
if (AppBskyEmbedRecord.isViewRecord(embed.record)) { if (bsky.isType(app.bsky.embed.record.viewRecord, embed.record)) {
const record = embed.record const record = embed.record
const path = postUriToRelativePath(record.uri, { const path = postUriToRelativePath(record.uri, {
handle: record.author.handle, handle: record.author.handle,
@@ -119,7 +114,7 @@ export function getMessageInfo({
} else { } else {
message = prefix(defaultEmbeddedContentMessage) message = prefix(defaultEmbeddedContentMessage)
} }
} else if (ChatBskyEmbedJoinLink.isView(lastMessage.embed)) { } else if (bsky.isType(chat.bsky.embed.joinLink.view, lastMessage.embed)) {
message = prefix(i18n._(msg`(chat invite link)`)) message = prefix(i18n._(msg`(chat invite link)`))
} else { } else {
message = prefix(defaultEmbeddedContentMessage) message = prefix(defaultEmbeddedContentMessage)
+9 -4
View File
@@ -1,10 +1,10 @@
import {ChatBskyConvoDefs} from '@atproto/api'
import {type I18n} from '@lingui/core' import {type I18n} from '@lingui/core'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {isDidBlockedInConvo} from '#/components/dms/getMessageInfo' import {isDidBlockedInConvo} from '#/components/dms/getMessageInfo'
import type * as bsky from '#/types/bsky' import {chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
export type UserReactionInfo = { export type UserReactionInfo = {
message: string message: string
@@ -18,12 +18,17 @@ export function getReactionInfo({
primaryProfile, primaryProfile,
i18n, i18n,
}: { }: {
convo: ChatBskyConvoDefs.ConvoView convo: chat.bsky.convo.defs.ConvoView
currentAccountDid: string | undefined currentAccountDid: string | undefined
primaryProfile?: bsky.profile.AnyProfileView primaryProfile?: bsky.profile.AnyProfileView
i18n: I18n i18n: I18n
}): UserReactionInfo | null { }): UserReactionInfo | null {
if (!ChatBskyConvoDefs.isMessageAndReactionView(convo.lastReaction)) { if (
!bsky.isType(
chat.bsky.convo.defs.messageAndReactionView,
convo.lastReaction,
)
) {
return null return null
} }
+44 -18
View File
@@ -1,4 +1,3 @@
import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
import {type MessageDescriptor} from '@lingui/core' import {type MessageDescriptor} from '@lingui/core'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
@@ -15,11 +14,13 @@ import {
Unlock_Stroke2_Corner2_Rounded as UnlockIcon, Unlock_Stroke2_Corner2_Rounded as UnlockIcon,
} from '#/components/icons/Lock' } from '#/components/icons/Lock'
import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil' import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil'
import {chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
export type SystemMessageAction = export type SystemMessageAction =
| { | {
kind: 'profile' kind: 'profile'
profile: ChatBskyActorDefs.ProfileViewBasic profile: chat.bsky.actor.defs.ProfileViewBasic
displayName: string displayName: string
} }
| {kind: 'inviteLink'} | {kind: 'inviteLink'}
@@ -31,8 +32,8 @@ export type SystemMessageInfo = {
} }
function getProfileAction( function getProfileAction(
user: ChatBskyConvoDefs.SystemMessageReferredUser, user: chat.bsky.convo.defs.SystemMessageReferredUser,
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>, relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>,
): Extract<SystemMessageAction, {kind: 'profile'}> | null { ): Extract<SystemMessageAction, {kind: 'profile'}> | null {
const profile = relatedProfiles.get(user.did) const profile = relatedProfiles.get(user.did)
if (!profile) return null if (!profile) return null
@@ -44,11 +45,11 @@ function getProfileAction(
} }
export function getSystemMessageInfo( export function getSystemMessageInfo(
data: ChatBskyConvoDefs.SystemMessageView['data'], data: chat.bsky.convo.defs.SystemMessageView['data'],
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>, relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>,
opts = {short: false}, opts = {short: false},
): SystemMessageInfo | null { ): SystemMessageInfo | null {
if (ChatBskyConvoDefs.isSystemMessageDataAddMember(data)) { if (bsky.isType(chat.bsky.convo.defs.systemMessageDataAddMember, data)) {
const action = getProfileAction(data.member, relatedProfiles) const action = getProfileAction(data.member, relatedProfiles)
return { return {
Icon: JoinIcon, Icon: JoinIcon,
@@ -61,7 +62,9 @@ export function getSystemMessageInfo(
: msg`Someone was added to the group`, : msg`Someone was added to the group`,
action: action ?? undefined, action: action ?? undefined,
} }
} else if (ChatBskyConvoDefs.isSystemMessageDataRemoveMember(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataRemoveMember, data)
) {
const action = getProfileAction(data.member, relatedProfiles) const action = getProfileAction(data.member, relatedProfiles)
return { return {
Icon: LeaveIcon, Icon: LeaveIcon,
@@ -74,7 +77,9 @@ export function getSystemMessageInfo(
: msg`Someone was removed from the group`, : msg`Someone was removed from the group`,
action: action ?? undefined, action: action ?? undefined,
} }
} else if (ChatBskyConvoDefs.isSystemMessageDataMemberJoin(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataMemberJoin, data)
) {
const action = getProfileAction(data.member, relatedProfiles) const action = getProfileAction(data.member, relatedProfiles)
return { return {
Icon: JoinIcon, Icon: JoinIcon,
@@ -87,7 +92,9 @@ export function getSystemMessageInfo(
: msg`Someone joined the group`, : msg`Someone joined the group`,
action: action ?? undefined, action: action ?? undefined,
} }
} else if (ChatBskyConvoDefs.isSystemMessageDataMemberLeave(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataMemberLeave, data)
) {
const action = getProfileAction(data.member, relatedProfiles) const action = getProfileAction(data.member, relatedProfiles)
return { return {
Icon: LeaveIcon, Icon: LeaveIcon,
@@ -100,13 +107,24 @@ export function getSystemMessageInfo(
: msg`Someone left the group`, : msg`Someone left the group`,
action: action ?? undefined, action: action ?? undefined,
} }
} else if (ChatBskyConvoDefs.isSystemMessageDataLockConvo(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataLockConvo, data)
) {
return {Icon: LockIcon, message: msg`Chat locked`} return {Icon: LockIcon, message: msg`Chat locked`}
} else if (ChatBskyConvoDefs.isSystemMessageDataUnlockConvo(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataUnlockConvo, data)
) {
return {Icon: UnlockIcon, message: msg`Chat unlocked`} return {Icon: UnlockIcon, message: msg`Chat unlocked`}
} else if (ChatBskyConvoDefs.isSystemMessageDataLockConvoPermanently(data)) { } else if (
bsky.isType(
chat.bsky.convo.defs.systemMessageDataLockConvoPermanently,
data,
)
) {
return {Icon: LockIcon, message: msg`Chat ended`} return {Icon: LockIcon, message: msg`Chat ended`}
} else if (ChatBskyConvoDefs.isSystemMessageDataEditGroup(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataEditGroup, data)
) {
return { return {
Icon: PencilIcon, Icon: PencilIcon,
message: message:
@@ -114,25 +132,33 @@ export function getSystemMessageInfo(
? msg`Chat title changed to ${data.newName}` ? msg`Chat title changed to ${data.newName}`
: msg`Chat title changed`, : msg`Chat title changed`,
} }
} else if (ChatBskyConvoDefs.isSystemMessageDataCreateJoinLink(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataCreateJoinLink, data)
) {
return { return {
Icon: ChainLinkIcon, Icon: ChainLinkIcon,
message: msg`Invite link created`, message: msg`Invite link created`,
action: {kind: 'inviteLink'}, action: {kind: 'inviteLink'},
} }
} else if (ChatBskyConvoDefs.isSystemMessageDataEditJoinLink(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataEditJoinLink, data)
) {
return { return {
Icon: ChainLinkIcon, Icon: ChainLinkIcon,
message: msg`Invite link edited`, message: msg`Invite link edited`,
action: {kind: 'inviteLink'}, action: {kind: 'inviteLink'},
} }
} else if (ChatBskyConvoDefs.isSystemMessageDataEnableJoinLink(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataEnableJoinLink, data)
) {
return { return {
Icon: ChainLinkIcon, Icon: ChainLinkIcon,
message: msg`Invite link enabled`, message: msg`Invite link enabled`,
action: {kind: 'inviteLink'}, action: {kind: 'inviteLink'},
} }
} else if (ChatBskyConvoDefs.isSystemMessageDataDisableJoinLink(data)) { } else if (
bsky.isType(chat.bsky.convo.defs.systemMessageDataDisableJoinLink, data)
) {
return { return {
Icon: ChainLinkBrokenIcon, Icon: ChainLinkBrokenIcon,
message: msg`Invite link disabled`, message: msg`Invite link disabled`,
+24 -17
View File
@@ -1,13 +1,8 @@
import {
AppBskyEmbedExternal,
AppBskyEmbedRecord,
type ChatBskyConvoDefs,
ChatBskyEmbedJoinLink,
ChatBskyGroupDefs,
} from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {BSKY_APP_HOST, toShortUrl} from '#/lib/strings/url-helpers' import {BSKY_APP_HOST, toShortUrl} from '#/lib/strings/url-helpers'
import {app, chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
/** /**
* Describes the embed of a quoted message that has no text of its own, so the * Describes the embed of a quoted message that has no text of its own, so the
@@ -21,13 +16,13 @@ type ReplyEmbedSummary =
| {type: 'unknown'} | {type: 'unknown'}
function summarizeReplyEmbed( function summarizeReplyEmbed(
embed: ChatBskyConvoDefs.MessageView['embed'], embed: chat.bsky.convo.defs.MessageView['embed'],
): ReplyEmbedSummary { ): ReplyEmbedSummary {
if (!AppBskyEmbedRecord.isView(embed)) return {type: 'unknown'} if (!bsky.isType(app.bsky.embed.record.view, embed)) return {type: 'unknown'}
const {record} = embed const {record} = embed
if (AppBskyEmbedRecord.isViewRecord(record)) { if (bsky.isType(app.bsky.embed.record.viewRecord, record)) {
const inner = record.embeds?.[0] const inner = record.embeds?.[0]
if (AppBskyEmbedExternal.isView(inner)) { if (bsky.isType(app.bsky.embed.external.view, inner)) {
return {type: 'external', uri: inner.external.uri} return {type: 'external', uri: inner.external.uri}
} }
return {type: 'post'} return {type: 'post'}
@@ -45,25 +40,32 @@ function summarizeReplyEmbed(
* callers can render it in a muted/italic style. * callers can render it in a muted/italic style.
*/ */
export function useReplyPreviewText(): ( export function useReplyPreviewText(): (
message: ChatBskyConvoDefs.MessageView, message: chat.bsky.convo.defs.MessageView,
) => {text: string; subtle: boolean} { ) => {text: string; subtle: boolean} {
const {t: l} = useLingui() const {t: l} = useLingui()
return (message: ChatBskyConvoDefs.MessageView) => { return (message: chat.bsky.convo.defs.MessageView) => {
const text = message.text const text = message.text
if (text.trim()) { if (text.trim()) {
return {text, subtle: false} return {text, subtle: false}
} }
if (ChatBskyEmbedJoinLink.isView(message.embed)) { if (bsky.isType(chat.bsky.embed.joinLink.view, message.embed)) {
const {joinLinkPreview} = message.embed const {joinLinkPreview} = message.embed
if (ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) { if (
bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, joinLinkPreview)
) {
return { return {
text: `${BSKY_APP_HOST}/chat/${joinLinkPreview.code}`, text: `${BSKY_APP_HOST}/chat/${joinLinkPreview.code}`,
subtle: true, subtle: true,
} }
} }
if (ChatBskyGroupDefs.isDisabledJoinLinkPreviewView(joinLinkPreview)) { if (
bsky.isType(
chat.bsky.group.defs.disabledJoinLinkPreviewView,
joinLinkPreview,
)
) {
return { return {
text: l({ text: l({
message: '(disabled chat invite link)', message: '(disabled chat invite link)',
@@ -72,7 +74,12 @@ export function useReplyPreviewText(): (
subtle: true, subtle: true,
} }
} }
if (ChatBskyGroupDefs.isInvalidJoinLinkPreviewView(joinLinkPreview)) { if (
bsky.isType(
chat.bsky.group.defs.invalidJoinLinkPreviewView,
joinLinkPreview,
)
) {
return { return {
text: l({ text: l({
message: '(invalid chat invite link)', message: '(invalid chat invite link)',
+13 -14
View File
@@ -1,4 +1,3 @@
import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api'
import {type $Typed} from '@atproto/lex' import {type $Typed} from '@atproto/lex'
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
@@ -78,7 +77,7 @@ export function localDateString(date: Date) {
} }
export function hasAlreadyReacted( export function hasAlreadyReacted(
message: ChatBskyConvoDefs.MessageView, message: chat.bsky.convo.defs.MessageView,
myDid: string | undefined, myDid: string | undefined,
emoji: string, emoji: string,
): boolean { ): boolean {
@@ -99,9 +98,9 @@ export function hasAlreadyReacted(
* already render anonymously ("Someone reacted"). * already render anonymously ("Someone reacted").
*/ */
export function filterBlockedReactions( export function filterBlockedReactions(
reactions: ChatBskyConvoDefs.ReactionView[] | undefined, reactions: chat.bsky.convo.defs.ReactionView[] | undefined,
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>, relatedProfiles: Map<string, chat.bsky.actor.defs.ProfileViewBasic>,
): ChatBskyConvoDefs.ReactionView[] { ): chat.bsky.convo.defs.ReactionView[] {
if (!reactions) return [] if (!reactions) return []
return reactions.filter(reaction => { return reactions.filter(reaction => {
const profile = relatedProfiles.get(reaction.sender.did) const profile = relatedProfiles.get(reaction.sender.did)
@@ -110,7 +109,7 @@ export function filterBlockedReactions(
} }
export function hasReachedReactionLimit( export function hasReachedReactionLimit(
message: ChatBskyConvoDefs.MessageView, message: chat.bsky.convo.defs.MessageView,
myDid: string | undefined, myDid: string | undefined,
): boolean { ): boolean {
if (!message.reactions) { if (!message.reactions) {
@@ -174,25 +173,25 @@ export function canReact({
return true return true
} }
export type GroupConvoMember = ChatBskyActorDefs.ProfileViewBasic & { export type GroupConvoMember = chat.bsky.actor.defs.ProfileViewBasic & {
// can be missing if account deleted // can be missing if account deleted
kind?: $Typed<ChatBskyActorDefs.GroupConvoMember> kind?: $Typed<chat.bsky.actor.defs.GroupConvoMember>
} }
export type DirectConvoMember = ChatBskyActorDefs.ProfileViewBasic & { export type DirectConvoMember = chat.bsky.actor.defs.ProfileViewBasic & {
kind: $Typed<ChatBskyActorDefs.DirectConvoMember> kind: $Typed<chat.bsky.actor.defs.DirectConvoMember>
} }
export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & ( export type ConvoWithDetails = {view: chat.bsky.convo.defs.ConvoView} & (
| { | {
kind: 'group' kind: 'group'
details: $Typed<ChatBskyConvoDefs.GroupConvo> details: $Typed<chat.bsky.convo.defs.GroupConvo>
primaryMember?: GroupConvoMember // the owner - may have left, thus optional primaryMember?: GroupConvoMember // the owner - may have left, thus optional
members: Array<GroupConvoMember> members: Array<GroupConvoMember>
} }
| { | {
kind: 'direct' kind: 'direct'
details: $Typed<ChatBskyConvoDefs.DirectConvo> details: $Typed<chat.bsky.convo.defs.DirectConvo>
primaryMember: DirectConvoMember // the other user primaryMember: DirectConvoMember // the other user
members: Array<DirectConvoMember> members: Array<DirectConvoMember>
} }
@@ -203,7 +202,7 @@ export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & (
* and enforces the correct type for convo members. * and enforces the correct type for convo members.
*/ */
export function parseConvoView( export function parseConvoView(
convoView: ChatBskyConvoDefs.ConvoView, convoView: chat.bsky.convo.defs.ConvoView,
ownDid: string | undefined, ownDid: string | undefined,
): ConvoWithDetails | null { ): ConvoWithDetails | null {
if (bsky.isType(chat.bsky.convo.defs.groupConvo, convoView.kind)) { if (bsky.isType(chat.bsky.convo.defs.groupConvo, convoView.kind)) {
@@ -1,5 +1,4 @@
import {View} from 'react-native' import {View} from 'react-native'
import {AppBskyEmbedVideo} from '@atproto/api'
import {type FeedPostSliceItem} from '#/state/queries/post-feed' import {type FeedPostSliceItem} from '#/state/queries/post-feed'
import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types'
@@ -10,6 +9,8 @@ import {
VideoPostCardPlaceholder, VideoPostCardPlaceholder,
} from '#/components/VideoPostCard' } from '#/components/VideoPostCard'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
export function PostFeedVideoGridRow({ export function PostFeedVideoGridRow({
items: slices, items: slices,
@@ -21,7 +22,7 @@ export function PostFeedVideoGridRow({
const ax = useAnalytics() const ax = useAnalytics()
const gutters = useGutters(['base', 'base', 0, 'base']) const gutters = useGutters(['base', 'base', 0, 'base'])
const posts = slices const posts = slices
.filter(slice => AppBskyEmbedVideo.isView(slice.post.embed)) .filter(slice => bsky.isType(app.bsky.embed.video.view, slice.post.embed))
.map(slice => ({ .map(slice => ({
post: slice.post, post: slice.post,
moderation: slice.moderation, moderation: slice.moderation,
+2 -2
View File
@@ -5,7 +5,6 @@ import Animated, {
useAnimatedRef, useAnimatedRef,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {type AppBskyEmbedImages} from '@atproto/api'
import {utils} from '@bsky.app/alf' import {utils} from '@bsky.app/alf'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -18,6 +17,7 @@ import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/compone
import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {type app} from '#/lexicons'
export function ConstrainedImage({ export function ConstrainedImage({
aspectRatio, aspectRatio,
@@ -71,7 +71,7 @@ export function AutoSizedImage({
onContainerRef, onContainerRef,
onDimsChange, onDimsChange,
}: { }: {
image: AppBskyEmbedImages.ViewImage image: app.bsky.embed.images.ViewImage
crop?: 'none' | 'square' | 'constrained' crop?: 'none' | 'square' | 'constrained'
onPress?: ( onPress?: (
containerRef: AnimatedRef<any>, containerRef: AnimatedRef<any>,
+3 -3
View File
@@ -14,7 +14,6 @@ import Animated, {
useAnimatedRef, useAnimatedRef,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {type AppBskyEmbedImages} from '@atproto/api'
import {utils} from '@bsky.app/alf' import {utils} from '@bsky.app/alf'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
@@ -41,12 +40,13 @@ import {PostEmbedViewContext} from '#/components/Post/Embed/types'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_ANDROID, IS_WEB} from '#/env' import {IS_ANDROID, IS_WEB} from '#/env'
import {type app} from '#/lexicons'
export * from './const' export * from './const'
export * from './maybeApplyGalleryOffsetStyles' export * from './maybeApplyGalleryOffsetStyles'
interface GalleryProps { interface GalleryProps {
images: AppBskyEmbedImages.ViewImage[] images: app.bsky.embed.images.ViewImage[]
onPress?: ( onPress?: (
index: number, index: number,
containerRefs: AnimatedRef<any>[], containerRefs: AnimatedRef<any>[],
@@ -404,7 +404,7 @@ function GalleryImage({
onPreviewPress, onPreviewPress,
}: { }: {
contentHeight: number contentHeight: number
image: AppBskyEmbedImages.ViewImage image: app.bsky.embed.images.ViewImage
index: number index: number
imageCount: number imageCount: number
onWidthChange: (index: number, width: number) => void onWidthChange: (index: number, width: number) => void
@@ -1,4 +1,3 @@
import {type AppBskyFeedDefs} from '@atproto/api'
import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation' import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation'
import {unique} from '#/lib/moderation' import {unique} from '#/lib/moderation'
@@ -17,7 +16,7 @@ export function maybeApplyGalleryOffsetStyles(
modui, modui,
additionalCauses, additionalCauses,
}: { }: {
post: AppBskyFeedDefs.PostView post: app.bsky.feed.defs.PostView
modui: ModerationUI modui: ModerationUI
additionalCauses?: ModerationCause[] | AppModerationCause[] additionalCauses?: ModerationCause[] | AppModerationCause[]
}, },
+3 -3
View File
@@ -1,15 +1,15 @@
import {useRef} from 'react' import {useRef} from 'react'
import {type StyleProp, View, type ViewStyle} from 'react-native' import {type StyleProp, View, type ViewStyle} from 'react-native'
import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated' import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated'
import {type AppBskyEmbedImages} from '@atproto/api'
import {atoms as a, useBreakpoints} from '#/alf' import {atoms as a, useBreakpoints} from '#/alf'
import {type Dimensions} from '#/components/Lightbox/types' import {type Dimensions} from '#/components/Lightbox/types'
import {type PostEmbedViewContext} from '#/components/Post/Embed/types' import {type PostEmbedViewContext} from '#/components/Post/Embed/types'
import {type app} from '#/lexicons'
import {GalleryItem} from './ImageLayoutGridItem' import {GalleryItem} from './ImageLayoutGridItem'
interface ImageLayoutGridProps { interface ImageLayoutGridProps {
images: AppBskyEmbedImages.ViewImage[] images: app.bsky.embed.images.ViewImage[]
onPress?: ( onPress?: (
index: number, index: number,
containerRefs: AnimatedRef<any>[], containerRefs: AnimatedRef<any>[],
@@ -45,7 +45,7 @@ export function ImageLayoutGrid({
} }
interface ImageLayoutGridInnerProps { interface ImageLayoutGridInnerProps {
images: AppBskyEmbedImages.ViewImage[] images: app.bsky.embed.images.ViewImage[]
onPress?: ( onPress?: (
index: number, index: number,
containerRefs: AnimatedRef<any>[], containerRefs: AnimatedRef<any>[],

Some files were not shown because too many files have changed in this diff Show More