migrate runtime call sites to lex clients and sdk actions

Phase 3 tasks 3-7 (parallel wave): composer/post pipeline on
pdsClient/appviewClient with structural+instance blob guards and a golden
CID fixture test; chat Convo/EventBus/queries on the dedicated chat client;
preferences sugar to SDK actions on the PDS client; remaining state/queries
producers (usePostThread unspecced flip, video scoped-token clients,
notifications, starter packs, lists) to client.call; UI runtime sweep
(AtUri from @atproto/syntax, moderation fns from @bsky.app/sdk/moderation,
SDK RichText, ozone reason tokens, guard rewrites via #/types/bsky).

Intermediate checkpoint (hooks skipped): ~114 typecheck errors remain in
cross-boundary consumer files, resolved by the type-only codemod next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-07-16 21:04:44 +03:00
parent b1a4e1cd16
commit 300a50b69a
296 changed files with 4507 additions and 3903 deletions
+5 -2
View File
@@ -1,11 +1,12 @@
import {useCallback, useEffect, useMemo, useState} from 'react'
import {type LayoutChangeEvent, View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {ChatBskyConvoDefs, moderateProfile} from '@atproto/api'
import {ChatBskyConvoDefs} from '@atproto/api'
import {
ScrollEdgeEffect,
ScrollEdgeEffectProvider,
} from '@bsky.app/expo-scroll-edge-effect'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {Trans, useLingui} from '@lingui/react/macro'
import {
type RouteProp,
@@ -45,6 +46,7 @@ import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util'
import {Error} from '#/components/Error'
import * as Layout from '#/components/Layout'
import {IS_LIQUID_GLASS} from '#/env'
import {toLex} from '#/types/bsky'
import {ChatDisabled} from './components/ChatDisabled'
import {ChatEnded} from './components/ChatEnded'
import {ChatLocked} from './components/ChatLocked'
@@ -225,7 +227,8 @@ function InnerReady({
const moderationOpts = useModerationOpts()
const primaryMemberModeration = useMemo(() => {
if (!primaryMember || !moderationOpts) return null
return moderateProfile(primaryMember, moderationOpts)
// TODO(phase4): drop toLex once useMaybeProfileShadow emits #/lexicons views
return moderateProfile(toLex(primaryMember), moderationOpts)
}, [primaryMember, moderationOpts])
const header = <MessagesListHeader convo={convo} />
@@ -1,5 +1,5 @@
import {View} from 'react-native'
import {moderateProfile} from '@atproto/api'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {Trans, useLingui} from '@lingui/react/macro'
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
@@ -21,6 +21,7 @@ import * as ProfileCard from '#/components/ProfileCard'
import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {toLex} from '#/types/bsky'
import {MemberMenu} from './MemberMenu'
import {RemoveMemberPrompt} from './prompts'
import {StatusBadge} from './StatusBadge'
@@ -80,7 +81,8 @@ export function Member({
return <MemberPlaceholder />
}
const moderation = moderateProfile(profile, moderationOpts)
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const isDeletedAccount = profile.handle === 'missing.invalid'
const displayName = isDeletedAccount
@@ -106,10 +108,13 @@ export function Member({
}
const joinedReason = profile.kind?.addedBy
? l`Added by ${createSanitizedDisplayName(
? // TODO(phase4): drop toLex once addedBy emits #/lexicons views
l`Added by ${createSanitizedDisplayName(
profile.kind.addedBy,
true,
moderateProfile(profile.kind.addedBy, moderationOpts).ui('displayName'),
moderateProfile(toLex(profile.kind.addedBy), moderationOpts).ui(
'displayName',
),
)}`
: l`Added by invite link`
@@ -1,11 +1,6 @@
import {useEffect, useState} from 'react'
import {Pressable, View} from 'react-native'
import {
ChatBskyActorDefs,
ChatBskyConvoDefs,
ChatBskyConvoUnlockConvo,
type ModerationOpts,
} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
@@ -19,6 +14,7 @@ import {
type NativeStackScreenProps,
type NavigationProp,
} from '#/lib/routes/types'
import {getErrorName} from '#/lib/xrpc-error'
import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useConvoQuery} from '#/state/queries/messages/conversation'
@@ -59,6 +55,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env'
import {chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {InviteLinkDialog} from '../components/InviteLinkDialog'
import {AddMembersLink} from './AddMembersLink'
@@ -172,16 +169,20 @@ function keyExtractor(item: Item) {
return item.key
}
/*
* The member list now comes from the migrated lexicon-typed query, but the
* narrowed `GroupConvoMember` target is still the old-typed shape from
* `#/components/dms/util` (migrates in a later task) - the guard doubles as
* the mixed-world bridge. TODO(phase4): retype to the lexicon member types
* once dms/util migrates.
*/
function isGroupMember(
member: ChatBskyActorDefs.ProfileViewBasic,
): member is GroupConvoMember {
member: chat.bsky.actor.defs.ProfileViewBasic,
): member is chat.bsky.actor.defs.ProfileViewBasic & GroupConvoMember {
// Kind is missing when the account has been deleted.
return (
member.kind === undefined ||
bsky.dangerousIsType<ChatBskyActorDefs.GroupConvoMember>(
member.kind,
ChatBskyActorDefs.isGroupConvoMember,
)
bsky.isType(chat.bsky.actor.defs.groupConvoMember, member.kind)
)
}
@@ -204,7 +205,15 @@ function GroupSettings({
const {data: memberListData = [], refetch} = useListConvoMembersQuery({
convoId: convo.view.id,
placeholderData: convo.members,
/*
* `convo.members` comes from the still-old-typed `#/components/dms/util`
* (migrates in a later task) while the member-list query is now typed on
* the lexicon ProfileViewBasic. TODO(phase4): drop toLex once dms/util
* migrates.
*/
placeholderData: bsky.toLex<chat.bsky.actor.defs.ProfileViewBasic[]>(
convo.members,
),
})
const {data: joinRequestsData, hasNextPage: hasMoreRequests} =
@@ -417,7 +426,7 @@ function SettingsHeader({
isPending: isLocking,
} = useLockConvo(convoId, {
onSuccess: (data, {silent}) => {
if (!ChatBskyConvoDefs.isGroupConvo(data.convo.kind)) return
if (!bsky.isType(chat.bsky.convo.defs.groupConvo, data.convo.kind)) return
if (silent) return
if (data.convo.kind.lockStatus === 'locked') {
ax.metric('groupchat:owner:lock', {convoId})
@@ -431,9 +440,7 @@ function SettingsHeader({
if (lock) {
logger.error('Failed to lock group chat', {message: e})
Toast.show(l`Failed to lock group chat`, {type: 'error'})
} else if (
e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError
) {
} else if (getErrorName(e) === 'ConvoLockedByModeration') {
Toast.show(l`This chat is locked by a moderation action`, {
type: 'error',
})
+10 -5
View File
@@ -1,8 +1,9 @@
import {useEffect} from 'react'
import {View} from 'react-native'
import {ImageBackground} from 'expo-image'
import {ChatBskyGroupDefs, moderateProfile} from '@atproto/api'
import {ChatBskyGroupDefs} from '@atproto/api'
import {type ThemeName} from '@bsky.app/alf'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {Trans, useLingui} from '@lingui/react/macro'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
@@ -20,6 +21,7 @@ import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/componen
import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {toLex} from '#/types/bsky'
const desktopDarkBg = require('../../../assets/images/chat-desktop-bg-dark.webp')
const desktopDimBg = require('../../../assets/images/chat-desktop-bg-dim.webp')
@@ -179,8 +181,9 @@ export function JoinRequest({setScreenState}: Props) {
{createSanitizedDisplayName(
joinLinkPreview.owner,
true,
// TODO(phase4): drop toLex once join link preview emits #/lexicons views
moderateProfile(
joinLinkPreview.owner,
toLex(joinLinkPreview.owner),
moderationOpts,
).ui('displayName'),
)}
@@ -215,12 +218,14 @@ export function JoinRequest({setScreenState}: Props) {
? l`Sign in to request access to this group chat.`
: l`Sign in to accept invite.`}{' '}
{joinLinkPreview.joinRule === 'followedByOwner' &&
// TODO(phase4): drop toLex once join link preview emits #/lexicons views
l`Only people ${createSanitizedDisplayName(
joinLinkPreview.owner,
true,
moderateProfile(joinLinkPreview.owner, moderationOpts).ui(
'displayName',
),
moderateProfile(
toLex(joinLinkPreview.owner),
moderationOpts,
).ui('displayName'),
)} follows can join.`}
</Text>
<ActionButtons setScreenState={setScreenState} />
+7 -21
View File
@@ -1,10 +1,6 @@
import {useState} from 'react'
import {View} from 'react-native'
import {
ChatBskyGroupApproveJoinRequest,
type ChatBskyGroupListJoinRequests,
ChatBskyGroupRejectJoinRequest,
} from '@atproto/api'
import {type ChatBskyGroupListJoinRequests} from '@atproto/api'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {type InfiniteData, useQueryClient} from '@tanstack/react-query'
@@ -16,6 +12,7 @@ import {
type NativeStackScreenProps,
type NavigationProp,
} from '#/lib/routes/types'
import {getErrorName} from '#/lib/xrpc-error'
import {logger} from '#/logger'
import {ConvoProvider, useConvo} from '#/state/messages/convo'
import {ConvoStatus} from '#/state/messages/convo/types'
@@ -190,18 +187,11 @@ function JoinRequestsList({
let errorMessage = l`Failed to accept join request`
if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.`
} else if (
error instanceof ChatBskyGroupApproveJoinRequest.InvalidConvoError
) {
} else if (getErrorName(error) === 'InvalidConvo') {
errorMessage = l`Conversation not found.`
} else if (
error instanceof ChatBskyGroupApproveJoinRequest.InsufficientRoleError
) {
} else if (getErrorName(error) === 'InsufficientRole') {
errorMessage = l`Only admins can accept join requests.`
} else if (
error instanceof
ChatBskyGroupApproveJoinRequest.MemberLimitReachedError
) {
} else if (getErrorName(error) === 'MemberLimitReached') {
errorMessage = l`The member limit has been reached.`
}
Toast.show(errorMessage, {type: 'error'})
@@ -223,13 +213,9 @@ function JoinRequestsList({
let errorMessage = l`Failed to reject join request`
if (isNetworkError(error)) {
errorMessage = l`A network error occurred. Please check your internet connection.`
} else if (
error instanceof ChatBskyGroupRejectJoinRequest.InvalidConvoError
) {
} else if (getErrorName(error) === 'InvalidConvo') {
errorMessage = l`Conversation not found.`
} else if (
error instanceof ChatBskyGroupRejectJoinRequest.InsufficientRoleError
) {
} else if (getErrorName(error) === 'InsufficientRole') {
errorMessage = l`Only admins can reject join requests.`
}
Toast.show(errorMessage, {type: 'error'})
@@ -1,12 +1,11 @@
import {useCallback, useState} from 'react'
import {type StyleProp, View, type ViewStyle} from 'react-native'
import {ToolsOzoneReportDefs} from '@atproto/api'
import {api} from '@bsky.app/sdk'
import {Trans, useLingui} from '@lingui/react/macro'
import {useMutation} from '@tanstack/react-query'
import {BLUESKY_MOD_SERVICE_HEADERS} from '#/lib/constants'
import {logger} from '#/logger'
import {useAgent, useSession} from '#/state/session'
import {usePdsClient, useSession} from '#/state/session'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -14,6 +13,8 @@ import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons
import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {com, tools} from '#/lexicons'
import {toLex} from '#/types/bsky'
export function ChatDisabled({
shape = 'pill',
@@ -92,25 +93,25 @@ function DialogInner() {
const control = Dialog.useDialogContext()
const [details, setDetails] = useState('')
const {gtMobile} = useBreakpoints()
const agent = useAgent()
const pdsClient = usePdsClient()
const {currentAccount} = useSession()
const {mutate, isPending} = useMutation({
mutationFn: async () => {
if (!currentAccount)
throw new Error('No current account, should be unreachable')
await agent.createModerationReport(
{
reasonType: ToolsOzoneReportDefs.REASONAPPEAL,
await pdsClient.call(
com.atproto.moderation.createReport,
toLex<com.atproto.moderation.createReport.$InputBody>({
reasonType: tools.ozone.report.defs.reasonAppeal.value,
subject: {
$type: 'com.atproto.admin.defs#repoRef',
did: currentAccount.did,
},
reason: details,
},
}),
{
encoding: 'application/json',
headers: BLUESKY_MOD_SERVICE_HEADERS,
service: api.moderation.service,
},
)
},
@@ -1,11 +1,11 @@
import {useCallback, useMemo, useState} from 'react'
import {type GestureResponderEvent, View} from 'react-native'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {
ChatBskyConvoDefs,
moderateProfile,
type ModerationDecision,
type ModerationOpts,
} from '@atproto/api'
} from '@bsky.app/sdk/moderation'
import {plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'
@@ -53,7 +53,8 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env'
import type * as bsky from '#/types/bsky'
import {chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {useIsWithinSplitView} from './splitView/context'
export const ChatListItemPortal = createPortalGroup()
@@ -125,7 +126,8 @@ function DirectChatItem({
const {isWithinLeftPanel} = useIsWithinSplitView()
const moderation = useMemo(
() => moderateProfile(profile, moderationOpts),
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views
() => moderateProfile(bsky.toLex(profile), moderationOpts),
[profile, moderationOpts],
)
@@ -192,7 +194,10 @@ function GroupChatItem({
const moderation = useMemo(
() =>
groupOwner ? moderateProfile(groupOwner, moderationOpts) : undefined,
groupOwner
? // TODO(phase4): drop toLex once useMaybeProfileShadow emits #/lexicons views
moderateProfile(bsky.toLex(groupOwner), moderationOpts)
: undefined,
[groupOwner, moderationOpts],
)
@@ -314,7 +319,12 @@ function BaseChatItem({
let lastMessageSentAt: string | null = null
// Deleted message
if (ChatBskyConvoDefs.isDeletedMessageView(convo.view.lastMessage)) {
if (
bsky.isType(
chat.bsky.convo.defs.deletedMessageView,
convo.view.lastMessage,
)
) {
lastMessageSentAt = convo.view.lastMessage.sentAt
lastMessage = isDeletedAccount
@@ -323,7 +333,7 @@ function BaseChatItem({
}
// Message
if (ChatBskyConvoDefs.isMessageView(convo.view.lastMessage)) {
if (bsky.isType(chat.bsky.convo.defs.messageView, convo.view.lastMessage)) {
const info = getMessageInfo({
convo: convo.view,
currentAccountDid: currentAccount?.did,
@@ -339,7 +349,12 @@ function BaseChatItem({
}
// Reaction
if (ChatBskyConvoDefs.isMessageAndReactionView(convo.view.lastReaction)) {
if (
bsky.isType(
chat.bsky.convo.defs.messageAndReactionView,
convo.view.lastReaction,
)
) {
const info = getReactionInfo({
convo: convo.view,
currentAccountDid: currentAccount?.did,
@@ -358,7 +373,12 @@ function BaseChatItem({
}
// System message
if (ChatBskyConvoDefs.isSystemMessageView(convo.view.lastMessage)) {
if (
bsky.isType(
chat.bsky.convo.defs.systemMessageView,
convo.view.lastMessage,
)
) {
const info = getSystemMessageInfo(
convo.view.lastMessage.data,
new Map(convo.view.members.map(m => [m.did, m])),
@@ -404,7 +424,15 @@ function BaseChatItem({
for (const member of convo.view.members) {
unstableCacheProfileView(queryClient, member)
}
precacheConvoQuery(queryClient, convo.view)
/*
* `convo.view` comes from the still-old-typed `#/components/dms/util`
* (migrates in a later task) while the convo cache is now keyed on the
* lexicon ConvoView. TODO(phase4): drop toLex once dms/util migrates.
*/
precacheConvoQuery(
queryClient,
bsky.toLex<chat.bsky.convo.defs.ConvoView>(convo.view),
)
void decrementBadgeCount(convo.view.unreadCount)
if (isDeletedAccount) {
e.preventDefault()
@@ -496,7 +524,13 @@ function BaseChatItem({
]
: undefined
}
onPressIn={() => precacheConvoQuery(queryClient, convo.view)}
onPressIn={() =>
// see onPress: old-typed dms/util view into the new-typed cache
precacheConvoQuery(
queryClient,
bsky.toLex<chat.bsky.convo.defs.ConvoView>(convo.view),
)
}
onPress={onPress}
onLongPress={showMenu && IS_NATIVE ? onLongPress : undefined}
onAccessibilityAction={showMenu ? onLongPress : undefined}>
@@ -1,10 +1,10 @@
import {Pressable} from 'react-native'
import {ChatBskyConvoUnlockConvo} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {HITSLOP_10} from '#/lib/constants'
import {type NavigationProp} from '#/lib/routes/types'
import {getErrorName} from '#/lib/xrpc-error'
import {logger} from '#/logger'
import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
import {useLockConvo} from '#/state/queries/messages/lock-conversation'
@@ -41,7 +41,7 @@ export function ChatLocked({
Toast.show(l({message: 'Group chat unlocked', context: 'toast'}))
},
onError: e => {
if (e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError) {
if (getErrorName(e) === 'ConvoLockedByModeration') {
Toast.show(l`This chat is locked by a moderation action`, {
type: 'error',
})
@@ -1,7 +1,7 @@
import {useCallback, useMemo} from 'react'
import {View} from 'react-native'
import {LinearGradient} from 'expo-linear-gradient'
import {moderateProfile} from '@atproto/api'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {Trans, useLingui} from '@lingui/react/macro'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
@@ -17,6 +17,7 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {usePromptControl} from '#/components/Prompt'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons'
export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
@@ -114,7 +115,8 @@ function InviterHeader({
const t = useTheme()
const profile = useProfileShadow(profileUnshadowed)
const moderation = useMemo(
() => moderateProfile(profile, moderationOpts),
// TODO(phase4): drop toLex once useProfileShadow emits #/lexicons views
() => moderateProfile(toLex(profile), moderationOpts),
[profile, moderationOpts],
)
const displayName = createSanitizedDisplayName(
@@ -1,11 +1,8 @@
import {useState} from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {
type ChatBskyGroupDefs,
moderateProfile,
type ModerationOpts,
} from '@atproto/api'
import {type ChatBskyGroupDefs} from '@atproto/api'
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
@@ -37,6 +34,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env'
import {toLex} from '#/types/bsky'
import {CopyTextButton} from './CopyTextButton'
import {EditTextButton} from './EditTextButton'
@@ -71,7 +69,8 @@ export function InviteLinkDialog({
const ownerName = createSanitizedDisplayName(
owner,
false,
moderateProfile(owner, moderationOpts).ui('displayName'),
// TODO(phase4): drop toLex once GroupConvoMember emits #/lexicons views
moderateProfile(toLex(owner), moderationOpts).ui('displayName'),
)
const {joinLink} = convo.details
@@ -13,7 +13,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {scheduleOnRN} from 'react-native-worklets'
import {GlassContainer} from 'expo-glass-effect'
import {LinearGradient} from 'expo-linear-gradient'
import {type $Typed, type ChatBskyConvoDefs} from '@atproto/api'
import {type $Typed} from '@atproto/lex'
import {ScrollEdgeEffect} from '@bsky.app/expo-scroll-edge-effect'
import {useLingui} from '@lingui/react/macro'
import {countGraphemes} from 'unicode-segmenter/grapheme'
@@ -37,6 +37,8 @@ import {PaperPlaneVertical_Filled_Stroke2_Corner1_Rounded as PaperPlaneIcon} fro
import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast'
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
import {type chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {type MessageEmbedState} from './MessageInputEmbed'
const MIN_HEIGHT = 40
@@ -53,7 +55,7 @@ export function MessageComposer({
onSendMessage: (
message: string,
embed?: MessageEmbedState,
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
replyTo?: $Typed<chat.bsky.convo.defs.MessageView>,
) => void
messageEmbed: MessageEmbedState | undefined
setEmbed: (embedUrl: string | undefined) => void
@@ -67,7 +69,16 @@ export function MessageComposer({
const editable = !needsEmailVerification && !loading
const {getDraft, clearDraft} = useMessageDraft()
const composerInternalApiRef = useComposerInternalApiRef()
const {replyTo, clearReply} = useMessageReplies()
const {replyTo: replyToOld, clearReply} = useMessageReplies()
/*
* `useMessageReplies` (`#/components/dms`, migrates in a later task) still
* emits the old `@atproto/api` MessageView; the send path is typed on the
* lexicon view. Structurally identical modulo branded strings.
* TODO(phase4): drop toLex once dms/MessageReplies migrates.
*/
const replyTo = bsky.toLex<chat.bsky.convo.defs.MessageView | null>(
replyToOld,
)
const [text, setText] = useState(getDraft)
useSaveMessageDraft(text)
@@ -106,7 +117,7 @@ export function MessageComposer({
const onSubmit = (
message: string,
embed: MessageEmbedState | undefined,
replyTo: ChatBskyConvoDefs.MessageView | null,
replyTo: chat.bsky.convo.defs.MessageView | null,
) => {
if (!editable) return
if (!embed && message.trim() === '') return
@@ -1,12 +1,8 @@
import {useCallback, useEffect, useMemo, useState} from 'react'
import {LayoutAnimation, View} from 'react-native'
import {
AppBskyFeedPost,
AppBskyRichtextFacet,
AtUri,
moderatePost,
RichText as RichTextAPI,
} from '@atproto/api'
import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {moderatePost} from '@bsky.app/sdk/moderation'
import {Trans, useLingui} from '@lingui/react/macro'
import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native'
@@ -36,6 +32,7 @@ import {ContentHider} from '#/components/moderation/ContentHider'
import {PostAlerts} from '#/components/moderation/PostAlerts'
import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
/**
@@ -166,13 +163,7 @@ function MessageInputPostEmbed({
)
const {rt, record} = useMemo(() => {
if (
post &&
bsky.dangerousIsType<AppBskyFeedPost.Record>(
post.record,
AppBskyFeedPost.isRecord,
)
) {
if (post && bsky.isType(app.bsky.feed.post, post.record)) {
return {
rt: new RichTextAPI({
text: post.record.text,
@@ -22,16 +22,9 @@ import Animated, {
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {scheduleOnRN} from 'react-native-worklets'
import {
type $Typed,
type AppBskyEmbedRecord,
AppBskyRichtextFacet,
ChatBskyConvoDefs,
type ChatBskyEmbedJoinLink,
ChatBskyGroupDefs,
RichText,
} from '@atproto/api'
import {type $Typed} from '@atproto/lex'
import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect'
import {RichText} from '@bsky.app/sdk/richtext'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {mergeRefs} from '#/lib/merge-refs'
@@ -52,7 +45,7 @@ import {type ConvoState, ConvoStatus} from '#/state/messages/convo/types'
import {useGetJoinLinkPreview} from '#/state/queries/join-links'
import {useGetPost} from '#/state/queries/post'
import {createEmbedViewRecordFromPost} from '#/state/queries/postgate/util'
import {useAgent, useSession} from '#/state/session'
import {usePdsClient, useSession} from '#/state/session'
import {List, type ListMethods} from '#/view/com/util/List'
import {MessageComposer} from '#/screens/Messages/components/MessageComposer'
import {MessageListError} from '#/screens/Messages/components/MessageListError'
@@ -71,6 +64,8 @@ import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env'
import {app, chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {ChatStatusInfo} from './ChatStatusInfo'
import {groupSystemMessages, type RenderItem} from './groupSystemMessages'
import {InviteLinkDialogProvider} from './InviteLinkDialogProvider'
@@ -116,8 +111,8 @@ function getNeighborMessage(
neighbor.type === 'deleted-message'
) {
if (
ChatBskyConvoDefs.isMessageView(neighbor.message) ||
ChatBskyConvoDefs.isDeletedMessageView(neighbor.message)
bsky.isType(chat.bsky.convo.defs.messageView, neighbor.message) ||
bsky.isType(chat.bsky.convo.defs.deletedMessageView, neighbor.message)
) {
return neighbor.message
}
@@ -144,7 +139,12 @@ export function MessagesList({
}) {
const ax = useAnalytics()
const convoState = useConvoActive()
const agent = useAgent()
/*
* Facet detection resolves handles via `com.atproto.identity.resolveHandle`,
* which the account (PDS) client serves - chat requires a session, so the
* client is always live here.
*/
const pdsClient = usePdsClient()
const {hasSession, currentAccount} = useSession()
const getPost = useGetPost()
const getJoinLinkPreview = useGetJoinLinkPreview()
@@ -520,7 +520,7 @@ export function MessagesList({
async (
text: string,
embedState?: MessageEmbedState,
reply?: $Typed<ChatBskyConvoDefs.MessageView>,
reply?: $Typed<chat.bsky.convo.defs.MessageView>,
) => {
let rt = new RichText({text: text.trimEnd()}, {cleanNewlines: true})
@@ -533,14 +533,14 @@ export function MessagesList({
rt.detectFacetsWithoutResolution()
let embed:
| $Typed<AppBskyEmbedRecord.Main>
| $Typed<ChatBskyEmbedJoinLink.Main>
| $Typed<app.bsky.embed.record.Main>
| $Typed<chat.bsky.embed.joinLink.Main>
| undefined
let embedView:
| $Typed<AppBskyEmbedRecord.View>
| $Typed<ChatBskyEmbedJoinLink.View>
| $Typed<app.bsky.embed.record.View>
| $Typed<chat.bsky.embed.joinLink.View>
| undefined
let replyTo: ChatBskyConvoDefs.ReplyRef | undefined
let replyTo: chat.bsky.convo.defs.ReplyRef | undefined
/**
* Find the embedded link facet and, if it's at the start or end of the
@@ -550,7 +550,8 @@ export function MessagesList({
const linkFacet = rt.facets?.find(facet =>
facet.features.find(
feature =>
AppBskyRichtextFacet.isLink(feature) && predicate(feature.uri),
bsky.isType(app.bsky.richtext.facet.link, feature) &&
predicate(feature.uri),
),
)
if (linkFacet) {
@@ -568,18 +569,24 @@ export function MessagesList({
try {
const post = await getPost({uri: embedState.uri})
if (post) {
embed = {
/*
* `post` comes from the still-old-typed `useGetPost` producer
* (migrates in a later task), so its uri/view shapes carry plain
* strings where the lexicon types are branded. TODO(phase4): drop
* toLex once that producer migrates.
*/
embed = bsky.toLex<$Typed<app.bsky.embed.record.Main>>({
$type: 'app.bsky.embed.record',
record: {
uri: post.uri,
cid: post.cid,
},
}
})
embedView = {
embedView = bsky.toLex<$Typed<app.bsky.embed.record.View>>({
$type: 'app.bsky.embed.record#view',
record: createEmbedViewRecordFromPost(post),
}
})
stripLinkFacet(uri => {
if (!isBskyPostUrl(uri)) return false
@@ -604,10 +611,14 @@ export function MessagesList({
const joinLinkPreview = await getJoinLinkPreview({code, hasSession})
if (joinLinkPreview) {
embedView = {
/*
* The preview comes from the still-old-typed `join-links.ts` query
* (migrates in a later task). TODO(phase4): drop toLex once it does.
*/
embedView = bsky.toLex<$Typed<chat.bsky.embed.joinLink.View>>({
$type: 'chat.bsky.embed.joinLink#view',
joinLinkPreview,
}
})
}
stripLinkFacet(uri => getChatInviteCodeFromUrl(uri) === code)
@@ -617,7 +628,7 @@ export function MessagesList({
replyTo = {messageId: reply.id}
}
await rt.detectFacets(agent)
await rt.detectFacets(pdsClient)
rt = shortenLinks(rt)
rt = stripInvalidMentions(rt)
@@ -658,7 +669,10 @@ export function MessagesList({
}
if (
embedView?.$type === 'chat.bsky.embed.joinLink#view' &&
ChatBskyGroupDefs.isJoinLinkPreviewView(embedView.joinLinkPreview)
bsky.isType(
chat.bsky.group.defs.joinLinkPreviewView,
embedView.joinLinkPreview,
)
) {
ax.metric('groupchat:inviteLink:shared', {
convoId: embedView.joinLinkPreview.convoId,
@@ -667,7 +681,7 @@ export function MessagesList({
}
},
[
agent,
pdsClient,
convoState,
getPost,
getJoinLinkPreview,
@@ -896,7 +910,7 @@ function Composer({
onSendMessage: (
message: string,
embed?: MessageEmbedState,
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
replyTo?: $Typed<chat.bsky.convo.defs.MessageView>,
) => Promise<void>
messageEmbed: MessageEmbedState | undefined
setEmbed: (embedUrl: string | undefined) => void
@@ -906,7 +920,7 @@ function Composer({
(
message: string,
embed?: MessageEmbedState,
replyTo?: $Typed<ChatBskyConvoDefs.MessageView>,
replyTo?: $Typed<chat.bsky.convo.defs.MessageView>,
) => {
void onSendMessage(message, embed, replyTo)
},
@@ -1,5 +1,5 @@
import {View} from 'react-native'
import {moderateProfile} from '@atproto/api'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
@@ -16,6 +16,7 @@ import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/P
import {ProfileBadges} from '#/components/ProfileBadges'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import {toLex} from '#/types/bsky'
export function MessagesListInfoPanel({
convo,
@@ -34,10 +35,11 @@ export function MessagesListInfoPanel({
)[0]
const handle = sanitizeHandle(profile.handle, '@')
const displayName = moderationOpts
? createSanitizedDisplayName(
? // TODO(phase4): drop toLex once convo members emit #/lexicons views
createSanitizedDisplayName(
profile,
true,
moderateProfile(profile, moderationOpts).ui('displayName'),
moderateProfile(toLex(profile), moderationOpts).ui('displayName'),
)
: handle
const profileLink =
@@ -1,11 +1,9 @@
import {View} from 'react-native'
import {
type ChatBskyGroupDefs,
ChatBskyGroupWithdrawJoinRequest,
} from '@atproto/api'
import {type ChatBskyGroupDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {isNetworkError} from '#/lib/strings/errors'
import {getErrorName} from '#/lib/xrpc-error'
import {useWithdrawJoinGroupChatRequest} from '#/state/queries/messages/withdraw-join-group-chat'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {atoms as a, useTheme, web} from '#/alf'
@@ -34,10 +32,7 @@ export function OutgoingRequestListItem({
let errorMessage = l`Failed to rescind your request. Please try again.`
if (isNetworkError(error)) {
errorMessage = l`There was a problem with your internet connection, please try again`
} else if (
error instanceof
ChatBskyGroupWithdrawJoinRequest.InvalidJoinRequestError
) {
} else if (getErrorName(error) === 'InvalidJoinRequest') {
errorMessage = l`Invalid rescind request.`
}
Toast.show(errorMessage)
@@ -41,6 +41,8 @@ import {Loader} from '#/components/Loader'
import * as Menu from '#/components/Menu'
import {ReportDialog} from '#/components/moderation/ReportDialog'
import * as Toast from '#/components/Toast'
import {type chat} from '#/lexicons'
import * as bsky from '#/types/bsky'
export function RejectMenu({
convo,
@@ -228,7 +230,18 @@ export function AcceptChatButton({
onMutate: () => {
onAcceptConvo?.()
if (currentScreen === 'list') {
precacheConvoQuery(queryClient, {...convo, status: 'accepted'})
/*
* `convo` is the old-typed view threaded from `#/components/dms/util`
* consumers while the convo cache is now keyed on the lexicon
* ConvoView. TODO(phase4): drop toLex once this file's props flip.
*/
precacheConvoQuery(
queryClient,
bsky.toLex<chat.bsky.convo.defs.ConvoView>({
...convo,
status: 'accepted',
}),
)
navigation.navigate('MessagesConversation', {
conversation: convo.id,
accept: true,
+1 -1
View File
@@ -1,6 +1,6 @@
import {Fragment, useCallback} from 'react'
import {Linking, View} from 'react-native'
import {LABELS} from '@atproto/api'
import {LABELS} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -4,7 +4,6 @@ import {
type AppBskyActorDefs,
type AppBskyActorProfile,
type AppBskyGraphDefs,
AppBskyGraphStarterpack,
type Un$Typed,
} from '@atproto/api'
import {TID} from '@atproto/common-web'
@@ -48,6 +47,7 @@ import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/ico
import {Loader} from '#/components/Loader'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {ValuePropositionPager} from './ValuePropositionPager'
@@ -209,10 +209,7 @@ export function StepFinished() {
usedStarterPack: Boolean(starterPack),
starterPackName:
starterPack &&
bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
starterPack.record,
AppBskyGraphStarterpack.isRecord,
)
bsky.isType(app.bsky.graph.starterpack, starterPack.record)
? starterPack.record.name
: undefined,
starterPackCreator: starterPack?.creator.did,
@@ -1,6 +1,6 @@
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {View} from 'react-native'
import {type ModerationOpts} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -1,6 +1,6 @@
import {useState} from 'react'
import {View} from 'react-native'
import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import {type AppBskyGraphDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -21,6 +21,7 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp'
@@ -102,12 +103,7 @@ export function StarterPackCard({
})
}
if (
!bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
record,
AppBskyGraphStarterpack.isRecord,
)
) {
if (!bsky.isType(app.bsky.graph.starterpack, record)) {
return null
}
@@ -1,5 +1,7 @@
import {View} from 'react-native'
import {type AppBskyFeedDefs, AtUri, moderateProfile} from '@atproto/api'
import {type AppBskyFeedDefs} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {makeProfileLink} from '#/lib/routes/links'
@@ -14,6 +16,7 @@ import {useFormatPostStatCount} from '#/components/PostControls/util'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {toLex} from '#/types/bsky'
const AVI_SIZE = 20
@@ -91,7 +94,8 @@ export function KnownLikers({post}: {post: AppBskyFeedDefs.PostView}) {
.map(like => like.actor)
.map(actor => ({
actor,
moderation: moderateProfile(actor, moderationOpts),
// TODO(phase4): drop toLex once useLikedBySampleQuery emits #/lexicons views
moderation: moderateProfile(toLex(actor), moderationOpts),
}))
.filter(({actor, moderation}) => {
const modui = moderation.ui('profileList')
@@ -4,9 +4,9 @@ import {
AppBskyFeedDefs,
AppBskyFeedPost,
type AppBskyFeedThreadgate,
AtUri,
RichText as RichTextAPI,
} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
@@ -58,6 +58,7 @@ import {Text} from '#/components/Typography'
import {WhoCanReply} from '#/components/WhoCanReply'
import {useAnalytics} from '#/analytics'
import {useActorStatus} from '#/features/liveNow'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
export function ThreadItemAnchor({
@@ -563,10 +564,7 @@ function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) {
const control = Prompt.usePromptControl()
const indexedAt = new Date(post.indexedAt)
const createdAt = bsky.dangerousIsType<AppBskyFeedPost.Record>(
post.record,
AppBskyFeedPost.isRecord,
)
const createdAt = bsky.isType(app.bsky.feed.post, post.record)
? new Date(post.record.createdAt)
: new Date(post.indexedAt)
@@ -1,11 +1,8 @@
import {memo, type ReactNode, useCallback, useMemo, useState} from 'react'
import {View} from 'react-native'
import {
type AppBskyFeedDefs,
type AppBskyFeedThreadgate,
AtUri,
RichText as RichTextAPI,
} from '@atproto/api'
import {type AppBskyFeedDefs, type AppBskyFeedThreadgate} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {Trans} from '@lingui/react/macro'
import {MAX_POST_LINES} from '#/lib/constants'
@@ -1,11 +1,8 @@
import {memo, useCallback, useMemo, useState} from 'react'
import {View} from 'react-native'
import {
type AppBskyFeedDefs,
type AppBskyFeedThreadgate,
AtUri,
RichText as RichTextAPI,
} from '@atproto/api'
import {type AppBskyFeedDefs, type AppBskyFeedThreadgate} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {Trans} from '@lingui/react/macro'
import {MAX_POST_LINES} from '#/lib/constants'
+2 -1
View File
@@ -1,5 +1,6 @@
import {View} from 'react-native'
import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api'
import {type AppBskyActorDefs} from '@atproto/api'
import {type ModerationDecision} from '@bsky.app/sdk/moderation'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
@@ -1,12 +1,8 @@
import {memo, useCallback, useMemo, useState} from 'react'
import {View} from 'react-native'
import {
type AppBskyActorDefs,
type AppBskyLabelerDefs,
moderateProfile,
type ModerationOpts,
type RichText as RichTextAPI,
} from '@atproto/api'
import {type AppBskyActorDefs, type AppBskyLabelerDefs} from '@atproto/api'
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {msg, plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
@@ -37,6 +33,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env'
import {toLex} from '#/types/bsky'
import {ProfileHeaderDisplayName} from './DisplayName'
import {EditProfileDialog} from './EditProfileDialog'
import {ProfileHeaderHandle} from './Handle'
@@ -70,7 +67,7 @@ let ProfileHeaderLabeler = ({
const isSelf = currentAccount?.did === profile.did
const moderation = useMemo(
() => moderateProfile(profile, moderationOpts),
() => moderateProfile(toLex(profile), moderationOpts),
[profile, moderationOpts],
)
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
@@ -1,12 +1,12 @@
import {memo, useMemo, useState} from 'react'
import {View} from 'react-native'
import {type AppBskyActorDefs} from '@atproto/api'
import {
type AppBskyActorDefs,
moderateProfile,
type ModerationDecision,
type ModerationOpts,
type RichText as RichTextAPI,
} from '@atproto/api'
} from '@bsky.app/sdk/moderation'
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -40,6 +40,7 @@ import {useAnalytics} from '#/analytics'
import {IS_IOS, IS_NATIVE} from '#/env'
import {InviteFriendsDialog} from '#/features/inviteFriends'
import {useActorStatus} from '#/features/liveNow'
import {toLex} from '#/types/bsky'
import {GermButton} from '../components/GermButton'
import {ProfileHeaderDisplayName} from './DisplayName'
import {EditProfileDialog} from './EditProfileDialog'
@@ -68,7 +69,7 @@ let ProfileHeaderStandard = ({
const {currentAccount} = useSession()
const {_} = useLingui()
const moderation = useMemo(
() => moderateProfile(profile, moderationOpts),
() => moderateProfile(toLex(profile), moderationOpts),
[profile, moderationOpts],
)
const [, queueUnblock] = useProfileBlockMutationQueue(profile)
+2 -1
View File
@@ -5,8 +5,9 @@ import Animated, {
useAnimatedRef,
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api'
import {type AppBskyActorDefs} from '@atproto/api'
import {utils} from '@bsky.app/alf'
import {type ModerationDecision} from '@bsky.app/sdk/moderation'
import {useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
+6 -8
View File
@@ -7,13 +7,9 @@ import Animated, {
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {scheduleOnRN} from 'react-native-worklets'
import {
type AppBskyActorDefs,
type AppBskyLabelerDefs,
moderateProfile,
type ModerationOpts,
type RichText as RichTextAPI,
} from '@atproto/api'
import {type AppBskyActorDefs, type AppBskyLabelerDefs} from '@atproto/api'
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {useIsFocused} from '@react-navigation/native'
import {sanitizeHandle} from '#/lib/strings/handles'
@@ -26,6 +22,7 @@ import {atoms as a, useTheme} from '#/alf'
import {Header} from '#/components/Layout'
import * as ProfileCard from '#/components/ProfileCard'
import {IS_NATIVE} from '#/env'
import {toLex} from '#/types/bsky'
import {
HeaderLabelerButtons,
ProfileHeaderLabeler,
@@ -115,7 +112,8 @@ const MinimalHeader = memo(function MinimalHeader({
const profile = useProfileShadow(profileUnshadowed)
const moderationOpts = useModerationOpts()
const moderation = useMemo(
() => (moderationOpts ? moderateProfile(profile, moderationOpts) : null),
() =>
moderationOpts ? moderateProfile(toLex(profile), moderationOpts) : null,
[moderationOpts, profile],
)
const [visible, setVisible] = useState(false)
+2 -2
View File
@@ -1,11 +1,11 @@
import {useCallback, useEffect, useImperativeHandle, useMemo} from 'react'
import {type ListRenderItemInfo, View} from 'react-native'
import {type AppBskyLabelerDefs} from '@atproto/api'
import {
type AppBskyLabelerDefs,
type InterpretedLabelValueDefinition,
interpretLabelValueDefinitions,
type ModerationOpts,
} from '@atproto/api'
} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
+28 -21
View File
@@ -1,6 +1,7 @@
import {Platform, View} from 'react-native'
import {Image} from 'expo-image'
import {type AppBskyActorDefs, type AppBskyActorGetProfile} from '@atproto/api'
import {type Client} from '@atproto/lex-client'
import {type DidString} from '@atproto/syntax'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -9,7 +10,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
import {until} from '#/lib/async/until'
import {isNetworkError} from '#/lib/strings/errors'
import {RQKEY} from '#/state/queries/profile'
import {type SessionAgent, useAgent, useSession} from '#/state/session'
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -20,13 +21,14 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {app, com} from '#/lexicons'
import type * as bsky from '#/types/bsky'
export function GermButton({
germ,
profile,
}: {
germ: AppBskyActorDefs.ProfileAssociatedGerm
germ: app.bsky.actor.defs.ProfileAssociatedGerm
profile: bsky.profile.AnyProfileView
}) {
const t = useTheme()
@@ -115,25 +117,26 @@ function GermSelfButton({did}: {did: string}) {
const ax = useAnalytics()
const {_} = useLingui()
const selfExplanationDialogControl = Dialog.useDialogControl()
const agent = useAgent()
const pdsClient = usePdsClient()
const appviewClient = useAppviewClient()
const queryClient = useQueryClient()
const {mutate: deleteDeclaration, isPending} = useMutation({
mutationFn: async () => {
const previousRecord = await agent.com.germnetwork.declaration
.get({
repo: did,
const previousRecord = await pdsClient
.get(com.germnetwork.declaration, {
repo: did as DidString,
rkey: 'self',
})
.then(res => res.value)
.catch(() => null)
await agent.com.germnetwork.declaration.delete({
repo: did,
await pdsClient.delete(com.germnetwork.declaration, {
repo: did as DidString,
rkey: 'self',
})
await whenAppViewReady(agent, did, res => !res.data.associated?.germ)
await whenAppViewReady(appviewClient, did, res => !res.associated?.germ)
return previousRecord
},
@@ -143,14 +146,15 @@ function GermSelfButton({did}: {did: string}) {
async function undo() {
if (!previousRecord) return
try {
await agent.com.germnetwork.declaration.put(
{
repo: did,
rkey: 'self',
},
previousRecord,
await pdsClient.put(com.germnetwork.declaration, previousRecord, {
repo: did as DidString,
rkey: 'self',
})
await whenAppViewReady(
appviewClient,
did,
res => !!res.associated?.germ,
)
await whenAppViewReady(agent, did, res => !!res.data.associated?.germ)
await queryClient.refetchQueries({queryKey: RQKEY(did)})
Toast.show(_(msg`Germ DM reconnected`))
@@ -273,7 +277,7 @@ function GermSelfButton({did}: {did: string}) {
}
function constructGermUrl(
declaration: AppBskyActorDefs.ProfileAssociatedGerm,
declaration: app.bsky.actor.defs.ProfileAssociatedGerm,
profile: bsky.profile.AnyProfileView,
viewerDid?: string,
) {
@@ -319,14 +323,17 @@ function platform() {
}
async function whenAppViewReady(
agent: SessionAgent,
appviewClient: Client,
actor: string,
fn: (res: AppBskyActorGetProfile.Response) => boolean,
fn: (res: app.bsky.actor.getProfile.$OutputBody) => boolean,
) {
await until(
5, // 5 tries
1e3, // 1s delay between tries
fn,
() => agent.app.bsky.actor.getProfile({actor}),
() =>
appviewClient.call(app.bsky.actor.getProfile, {
actor: actor as DidString,
}),
)
}
@@ -1,6 +1,6 @@
import {useCallback, useMemo, useState} from 'react'
import {View} from 'react-native'
import {AtUri} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {TRENDING_HANDLE} from '#/lib/constants'
@@ -1,6 +1,7 @@
import {useMemo} from 'react'
import {View} from 'react-native'
import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
import {AppBskyGraphDefs} from '@atproto/api'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -23,6 +24,7 @@ import {Loader} from '#/components/Loader'
import {RichText} from '#/components/RichText'
import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {toLex} from '#/types/bsky'
import {MoreOptionsMenu} from './MoreOptionsMenu'
import {SubscribeMenu} from './SubscribeMenu'
@@ -128,7 +130,8 @@ export function Header({
list.description
? new RichTextAPI({
text: list.description,
facets: list.descriptionFacets,
// TODO(phase4): drop toLex once the list view producer emits #/lexicons facets
facets: toLex(list.descriptionFacets),
})
: undefined,
[list],
@@ -1,4 +1,5 @@
import {type AppBskyActorDefs, AppBskyGraphDefs, AtUri} from '@atproto/api'
import {type AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
+6 -7
View File
@@ -1,12 +1,9 @@
import {useCallback, useMemo, useRef, useState} from 'react'
import {View} from 'react-native'
import {useAnimatedRef} from 'react-native-reanimated'
import {
AppBskyGraphDefs,
AtUri,
moderateUserList,
type ModerationOpts,
} from '@atproto/api'
import {AppBskyGraphDefs} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {moderateUserList, type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -42,6 +39,7 @@ import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
import * as Hider from '#/components/moderation/Hider'
import {IS_WEB} from '#/env'
import {toLex} from '#/types/bsky'
import {AboutSection} from './AboutSection'
import {ErrorScreen} from './components/ErrorScreen'
import {Header} from './components/Header'
@@ -169,7 +167,8 @@ function ProfileListScreenLoaded({
const [headerHeight, setHeaderHeight] = useState<number | null>(null)
const moderation = useMemo(() => {
return moderateUserList(list, moderationOpts)
// TODO(phase4): drop toLex once ListView prop emits #/lexicons views
return moderateUserList(toLex(list), moderationOpts)
}, [list, moderationOpts])
useSetTitle(isHidden ? _(msg`List Hidden`) : list.name)
@@ -1,6 +1,7 @@
import {useMemo} from 'react'
import {View} from 'react-native'
import {type AppBskyFeedDefs, AtUri} from '@atproto/api'
import {type AppBskyFeedDefs} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {PressableScale} from '#/lib/custom-animations/PressableScale'
import {makeCustomFeedLink} from '#/lib/routes/links'
@@ -1,5 +1,5 @@
import {ScrollView, View} from 'react-native'
import {moderateProfile, type ModerationOpts} from '@atproto/api'
import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {createHitslop} from '#/lib/constants'
@@ -22,6 +22,7 @@ import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky'
import {toLex} from '#/types/bsky'
export function SearchHistory({
searchHistory,
@@ -206,7 +207,8 @@ function RecentProfileItem({
const {t: l} = useLingui()
const width = 80
const moderation = moderateProfile(profile, moderationOpts)
// TODO(phase4): drop toLex once profile prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
@@ -1,6 +1,6 @@
import {useCallback} from 'react'
import {View} from 'react-native'
import {type ModerationOpts} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
@@ -1,10 +1,7 @@
import {useState} from 'react'
import {View} from 'react-native'
import {
type AppBskyGraphDefs,
AppBskyGraphStarterpack,
moderateProfile,
} from '@atproto/api'
import {type AppBskyGraphDefs} from '@atproto/api'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -22,6 +19,7 @@ import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {useStarterPackLink} from '#/components/StarterPack/StarterPackCard'
import {SubtleHover} from '#/components/SubtleHover'
import {Text} from '#/components/Typography'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
export function StarterPackCard({
@@ -38,12 +36,7 @@ export function StarterPackCard({
const link = useStarterPackLink({view})
const record = view.record
if (
!bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
record,
AppBskyGraphStarterpack.isRecord,
)
) {
if (!bsky.isType(app.bsky.graph.starterpack, record)) {
return null
}
@@ -1,6 +1,7 @@
import {memo, useEffect} from 'react'
import {View} from 'react-native'
import {type AppBskyActorSearchActors, type ModerationOpts} from '@atproto/api'
import {type AppBskyActorSearchActors} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {type InfiniteData} from '@tanstack/react-query'
@@ -1,11 +1,9 @@
import {useMemo} from 'react'
import {Pressable, View} from 'react-native'
import {Image} from 'expo-image'
import {
type AppBskyUnspeccedDefs,
moderateProfile,
RichText as RichTextApi,
} from '@atproto/api'
import {type AppBskyUnspeccedDefs} from '@atproto/api'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {RichText as RichTextApi} from '@bsky.app/sdk/richtext'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -31,6 +29,7 @@ import {useTrendingTopicSeen} from '#/components/TrendingTopics'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import * as ModuleHeader from '../components/ModuleHeader'
import {toLex} from '#/types/bsky'
const IMAGE_SIZE = 56
@@ -308,7 +307,8 @@ function useModerateTrendingActors(
return actors
.filter(actor => {
const decision = moderateProfile(actor, moderationOpts)
// TODO(phase4): drop toLex once TrendView actors emit #/lexicons views
const decision = moderateProfile(toLex(actor), moderationOpts)
return !decision.ui('avatar').filter && !decision.ui('avatar').blur
})
.slice(0, 3)
@@ -1,6 +1,7 @@
import {useMemo} from 'react'
import {ScrollView, View} from 'react-native'
import {AppBskyEmbedVideo, AtUri} from '@atproto/api'
import {AppBskyEmbedVideo} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -1,5 +1,5 @@
import {View} from 'react-native'
import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api'
import {type $Typed, type ComAtprotoLabelDefs} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
import {useQueryClient} from '@tanstack/react-query'
@@ -22,6 +22,7 @@ import {Text} from '#/components/Typography'
import {useSimpleVerificationState} from '#/components/verification'
import {VerificationCheck} from '#/components/verification/VerificationCheck'
import {useAnalytics} from '#/analytics'
import {com} from '#/lexicons'
import * as bsky from '#/types/bsky'
type Props = NativeStackScreenProps<
@@ -53,9 +54,9 @@ export function AutomationLabelSettingsScreen({}: Props) {
{
profile,
updates: existing => {
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.validate(
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.matches(
com.atproto.label.defs.selfLabels,
existing.labels,
ComAtprotoLabelDefs.validateSelfLabels,
)
? existing.labels
: {
@@ -4,8 +4,8 @@ import * as Contacts from 'expo-contacts'
import {
type AppBskyContactDefs,
type AppBskyContactGetSyncStatus,
type ModerationOpts,
} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
+9 -3
View File
@@ -1,7 +1,8 @@
import {useState} from 'react'
import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native'
import {useReducedMotion} from 'react-native-reanimated'
import {type AppBskyActorDefs, moderateProfile} from '@atproto/api'
import {type AppBskyActorDefs} from '@atproto/api'
import {moderateProfile} from '@bsky.app/sdk/moderation'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
@@ -69,6 +70,7 @@ import {IS_INTERNAL, IS_IOS, IS_NATIVE} from '#/env'
import {useActorStatus} from '#/features/liveNow'
import {device, useStorage} from '#/storage'
import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged'
import {toLex} from '#/types/bsky'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
export function SettingsScreen({}: Props) {
@@ -329,7 +331,8 @@ function ProfilePreview({
if (!moderationOpts) return null
const moderation = moderateProfile(profile, moderationOpts)
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views
const moderation = moderateProfile(toLex(profile), moderationOpts)
const displayName = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
@@ -636,7 +639,10 @@ function AccountRow({
<UserAvatar
size={28}
avatar={profile.avatar}
moderation={moderateProfile(profile, moderationOpts).ui('avatar')}
// TODO(phase4): drop toLex once ProfileViewDetailed prop emits #/lexicons views
moderation={moderateProfile(toLex(profile), moderationOpts).ui(
'avatar',
)}
type={profile.associated?.labeler ? 'labeler' : 'user'}
live={live}
hideLiveBadge
@@ -1,6 +1,6 @@
import {useCallback} from 'react'
import {View} from 'react-native'
import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api'
import {type $Typed, type ComAtprotoLabelDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -13,6 +13,7 @@ import {useSession} from '#/state/session'
import {atoms as a, useTheme} from '#/alf'
import * as Toggle from '#/components/forms/Toggle'
import {Text} from '#/components/Typography'
import {com} from '#/lexicons'
import * as bsky from '#/types/bsky'
export function PwiOptOut() {
@@ -35,9 +36,9 @@ export function PwiOptOut() {
profile,
updates: existing => {
// create labels attr if needed
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.validate(
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.matches(
com.atproto.label.defs.selfLabels,
existing.labels,
ComAtprotoLabelDefs.validateSelfLabels,
)
? existing.labels
: {
+2 -5
View File
@@ -3,7 +3,6 @@ import {AppState, type AppStateStatus, View} from 'react-native'
import ReactNativeDeviceAttest from 'react-native-device-attest'
import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
import Animated, {FadeIn, LayoutAnimationConfig} from 'react-native-reanimated'
import {AppBskyGraphStarterpack} from '@atproto/api'
import {tokens} from '@bsky.app/alf'
import {Trans, useLingui} from '@lingui/react/macro'
@@ -32,6 +31,7 @@ import {ScreenTransition} from '#/components/ScreenTransition'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {GCP_PROJECT_ID, IS_ANDROID} from '#/env'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
export function Signup({onPressBack}: {onPressBack: () => void}) {
@@ -141,10 +141,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
scrollable>
<View testID="createAccount" style={a.flex_1}>
{showStarterPackCard &&
bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
starterPack.record,
AppBskyGraphStarterpack.isRecord,
) ? (
bsky.isType(app.bsky.graph.starterpack, starterPack.record) ? (
<Animated.View
entering={!isFetchedAtMount ? FadeIn : undefined}>
<LinearGradientBackground
@@ -1,12 +1,9 @@
import {useEffect, useState} from 'react'
import {Pressable, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {
AppBskyGraphDefs,
AppBskyGraphStarterpack,
AtUri,
type ModerationOpts,
} from '@atproto/api'
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -38,6 +35,7 @@ import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB, IS_WEB_MOBILE_ANDROID} from '#/env'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
@@ -86,12 +84,7 @@ export function LandingScreen({
}
// Just for types, this cannot be hit
if (
!bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
starterPack.record,
AppBskyGraphStarterpack.isRecord,
)
) {
if (!bsky.isType(app.bsky.graph.starterpack, starterPack.record)) {
return null
}
+8 -14
View File
@@ -1,13 +1,10 @@
import {useCallback, useEffect, useState} from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {
AppBskyGraphDefs,
AppBskyGraphStarterpack,
AtUri,
type ModerationOpts,
RichText as RichTextAPI,
} from '@atproto/api'
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
@@ -77,6 +74,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
type StarterPackScreeProps = NativeStackScreenProps<
@@ -406,19 +404,15 @@ function Header({
})
}
if (
!bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
record,
AppBskyGraphStarterpack.isRecord,
)
) {
if (!bsky.isType(app.bsky.graph.starterpack, record)) {
return null
}
const richText = record.description
? new RichTextAPI({
text: record.description,
facets: record.descriptionFacets,
// TODO(phase4): drop toLex once the starterpack record producer emits #/lexicons facets
facets: bsky.toLex(record.descriptionFacets),
})
: undefined
+3 -6
View File
@@ -1,13 +1,10 @@
import {createContext, useContext, useReducer} from 'react'
import {
type AppBskyFeedDefs,
type AppBskyGraphDefs,
AppBskyGraphStarterpack,
} from '@atproto/api'
import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api'
import {msg, plural} from '@lingui/core/macro'
import {STARTER_PACK_MAX_SIZE} from '#/lib/constants'
import * as Toast from '#/components/Toast'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
const steps = ['Details', 'Profiles', 'Feeds'] as const
@@ -135,7 +132,7 @@ export function Provider({
if (
starterPack &&
bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord)
bsky.matches(app.bsky.graph.starterpack, starterPack.record)
) {
return {
canNext: true,
+2 -1
View File
@@ -1,7 +1,8 @@
import {useState} from 'react'
import {type ListRenderItemInfo, View} from 'react-native'
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
import {type AppBskyFeedDefs, type ModerationOpts} from '@atproto/api'
import {type AppBskyFeedDefs} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {Trans} from '@lingui/react/macro'
import {DISCOVER_FEED_URI} from '#/lib/constants'
@@ -1,7 +1,7 @@
import {useState} from 'react'
import {type ListRenderItemInfo, View} from 'react-native'
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
import {type ModerationOpts} from '@atproto/api'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {Trans} from '@lingui/react/macro'
import {useA11y} from '#/state/a11y'
+2 -2
View File
@@ -7,9 +7,9 @@ import {
type AppBskyActorDefs,
type AppBskyFeedDefs,
type AppBskyGraphDefs,
AtUri,
type ModerationOpts,
} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {type ModerationOpts} from '@bsky.app/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
+13 -14
View File
@@ -2,19 +2,16 @@ import {useState} from 'react'
import {View} from 'react-native'
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {type ComAtprotoAdminDefs, ToolsOzoneReportDefs} from '@atproto/api'
import {api} from '@bsky.app/sdk'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {useMutation} from '@tanstack/react-query'
import {countGraphemes} from 'unicode-segmenter/grapheme'
import {
BLUESKY_MOD_SERVICE_HEADERS,
MAX_REPORT_REASON_GRAPHEME_LENGTH,
} from '#/lib/constants'
import {MAX_REPORT_REASON_GRAPHEME_LENGTH} from '#/lib/constants'
import {cleanError} from '#/lib/strings/errors'
import {useAgent, useSession, useSessionApi} from '#/state/session'
import {usePdsClient, useSession, useSessionApi} from '#/state/session'
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
import {Logo} from '#/view/icons/Logo'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
@@ -24,6 +21,8 @@ import {SimpleInlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader'
import {P, Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
import {com, tools} from '#/lexicons'
import {toLex} from '#/types/bsky'
const COL_WIDTH = 400
@@ -34,7 +33,7 @@ export function Takendown() {
const {gtMobile} = useBreakpoints()
const {currentAccount} = useSession()
const {logoutCurrentAccount} = useSessionApi()
const agent = useAgent()
const pdsClient = usePdsClient()
const [isAppealling, setIsAppealling] = useState(false)
const [reason, setReason] = useState('')
@@ -50,18 +49,18 @@ export function Takendown() {
} = useMutation({
mutationFn: async (appealText: string) => {
if (!currentAccount) throw new Error('No session')
await agent.com.atproto.moderation.createReport(
{
reasonType: ToolsOzoneReportDefs.REASONAPPEAL,
await pdsClient.call(
com.atproto.moderation.createReport,
toLex<com.atproto.moderation.createReport.$InputBody>({
reasonType: tools.ozone.report.defs.reasonAppeal.value,
subject: {
$type: 'com.atproto.admin.defs#repoRef',
did: currentAccount.did,
} satisfies ComAtprotoAdminDefs.RepoRef,
},
reason: appealText,
},
}),
{
encoding: 'application/json',
headers: BLUESKY_MOD_SERVICE_HEADERS,
service: api.moderation.service,
},
)
},
+8 -13
View File
@@ -25,14 +25,10 @@ import {useEvent, useEventListener} from 'expo'
import {Image, type ImageStyle} from 'expo-image'
import {LinearGradient} from 'expo-linear-gradient'
import {createVideoPlayer, type VideoPlayer, VideoView} from 'expo-video'
import {
AppBskyEmbedVideo,
type AppBskyFeedDefs,
AppBskyFeedPost,
AtUri,
type ModerationDecision,
RichText as RichTextAPI,
} from '@atproto/api'
import {AppBskyEmbedVideo, type AppBskyFeedDefs} from '@atproto/api'
import {AtUri} from '@atproto/syntax'
import {type ModerationDecision} from '@bsky.app/sdk/moderation'
import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext'
import {Trans, useLingui} from '@lingui/react/macro'
import {
type RouteProp,
@@ -102,6 +98,7 @@ import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_ANDROID} from '#/env'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky'
import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber'
@@ -804,15 +801,13 @@ function Overlay({
)
const rkey = new AtUri(post.uri).rkey
const record = bsky.dangerousIsType<AppBskyFeedPost.Record>(
post.record,
AppBskyFeedPost.isRecord,
)
const record = bsky.isType(app.bsky.feed.post, post.record)
? post.record
: undefined
const richText = new RichTextAPI({
text: record?.text || '',
facets: record?.facets,
// TODO(phase4): drop toLex once the post record producer emits #/lexicons facets
facets: bsky.toLex(record?.facets),
})
const handle = sanitizeHandle(post.author.handle, '@')