import React from 'react' import {View} from 'react-native' import { AppBskyActorDefs, AppBskyFeedDefs, AppBskyFeedPost, ComAtprotoLabelDefs, interpretLabelValueDefinition, LabelPreference, LABELS, mock, moderatePost, moderateProfile, ModerationBehavior, ModerationDecision, ModerationOpts, RichText, } from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' import {FeedNotification} from '#/state/queries/notifications/types' import { groupNotifications, shouldFilterNotif, } from '#/state/queries/notifications/util' import {moderationOptsOverrideContext} from '#/state/queries/preferences' import {useSession} from '#/state/session' import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' import {CenteredView, ScrollView} from '#/view/com/util/Views' import {ProfileHeaderStandard} from '#/screens/Profile/Header/ProfileHeaderStandard' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Divider} from '#/components/Divider' import * as Toggle from '#/components/forms/Toggle' import * as ToggleButton from '#/components/forms/ToggleButton' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import { ChevronBottom_Stroke2_Corner0_Rounded as ChevronBottom, ChevronTop_Stroke2_Corner0_Rounded as ChevronTop, } from '#/components/icons/Chevron' import {H1, H3, P, Text} from '#/components/Typography' import {ScreenHider} from '../../components/moderation/ScreenHider' import {FeedItem as NotifFeedItem} from '../com/notifications/FeedItem' import {PostThreadItem} from '../com/post-thread/PostThreadItem' import {FeedItem} from '../com/posts/FeedItem' import {ProfileCard} from '../com/profile/ProfileCard' const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys( LABELS, ) as (keyof typeof LABELS)[] export const DebugModScreen = ({}: NativeStackScreenProps< CommonNavigatorParams, 'DebugMod' >) => { const t = useTheme() const [scenario, setScenario] = React.useState(['label']) const [scenarioSwitches, setScenarioSwitches] = React.useState([]) const [label, setLabel] = React.useState([LABEL_VALUES[0]]) const [target, setTarget] = React.useState(['account']) const [visibility, setVisiblity] = React.useState(['warn']) const [customLabelDef, setCustomLabelDef] = React.useState({ identifier: 'custom', blurs: 'content', severity: 'alert', defaultSetting: 'warn', locales: [ { lang: 'en', name: 'Custom label', description: 'A custom label created in this test environment', }, ], }) const [view, setView] = React.useState(['post']) const labelStrings = useGlobalLabelStrings() const {currentAccount} = useSession() const isTargetMe = scenario[0] === 'label' && scenarioSwitches.includes('targetMe') const isSelfLabel = scenario[0] === 'label' && scenarioSwitches.includes('selfLabel') const noAdult = scenario[0] === 'label' && scenarioSwitches.includes('noAdult') const isLoggedOut = scenario[0] === 'label' && scenarioSwitches.includes('loggedOut') const isFollowing = scenarioSwitches.includes('following') const did = isTargetMe && currentAccount ? currentAccount.did : 'did:web:bob.test' const profile = React.useMemo(() => { const mockedProfile = mock.profileViewBasic({ handle: `bob.test`, displayName: 'Bob Robertson', description: 'User with this as their bio', labels: scenario[0] === 'label' && target[0] === 'account' ? [ mock.label({ src: isSelfLabel ? did : undefined, val: label[0], uri: `at://${did}/`, }), ] : scenario[0] === 'label' && target[0] === 'profile' ? [ mock.label({ src: isSelfLabel ? did : undefined, val: label[0], uri: `at://${did}/app.bsky.actor.profile/self`, }), ] : undefined, viewer: mock.actorViewerState({ following: isFollowing ? `at://${currentAccount?.did || ''}/app.bsky.graph.follow/1234` : undefined, muted: scenario[0] === 'mute', mutedByList: undefined, blockedBy: undefined, blocking: scenario[0] === 'block' ? `at://did:web:alice.test/app.bsky.actor.block/fake` : undefined, blockingByList: undefined, }), }) mockedProfile.did = did mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png' mockedProfile.banner = 'https://bsky.social/about/images/social-card-default-gradient.png' return mockedProfile }, [scenario, target, label, isSelfLabel, did, isFollowing, currentAccount]) const post = React.useMemo(() => { return mock.postView({ record: mock.post({ text: "This is the body of the post. It's where the text goes. You get the idea.", }), author: profile, labels: scenario[0] === 'label' && target[0] === 'post' ? [ mock.label({ src: isSelfLabel ? did : undefined, val: label[0], uri: `at://${did}/app.bsky.feed.post/fake`, }), ] : undefined, embed: target[0] === 'embed' ? mock.embedRecordView({ record: mock.post({ text: 'Embed', }), labels: scenario[0] === 'label' && target[0] === 'embed' ? [ mock.label({ src: isSelfLabel ? did : undefined, val: label[0], uri: `at://${did}/app.bsky.feed.post/fake`, }), ] : undefined, author: profile, }) : { $type: 'app.bsky.embed.images#view', images: [ { thumb: 'https://bsky.social/about/images/social-card-default-gradient.png', fullsize: 'https://bsky.social/about/images/social-card-default-gradient.png', alt: '', }, ], }, }) }, [scenario, label, target, profile, isSelfLabel, did]) const replyNotif = React.useMemo(() => { const notif = mock.replyNotification({ record: mock.post({ text: "This is the body of the post. It's where the text goes. You get the idea.", reply: { parent: { uri: `at://${did}/app.bsky.feed.post/fake-parent`, cid: 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq', }, root: { uri: `at://${did}/app.bsky.feed.post/fake-parent`, cid: 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq', }, }, }), author: profile, labels: scenario[0] === 'label' && target[0] === 'post' ? [ mock.label({ src: isSelfLabel ? did : undefined, val: label[0], uri: `at://${did}/app.bsky.feed.post/fake`, }), ] : undefined, }) const [item] = groupNotifications([notif]) item.subject = mock.postView({ record: notif.record as AppBskyFeedPost.Record, author: profile, labels: notif.labels, }) return item }, [scenario, label, target, profile, isSelfLabel, did]) const followNotif = React.useMemo(() => { const notif = mock.followNotification({ author: profile, subjectDid: currentAccount?.did || '', }) const [item] = groupNotifications([notif]) return item }, [profile, currentAccount]) const modOpts = React.useMemo(() => { return { userDid: isLoggedOut ? '' : isTargetMe ? did : 'did:web:alice.test', prefs: { adultContentEnabled: !noAdult, labels: { [label[0]]: visibility[0] as LabelPreference, }, labelers: [ { did: 'did:plc:fake-labeler', labels: {[label[0]]: visibility[0] as LabelPreference}, }, ], mutedWords: [], hiddenPosts: [], }, labelDefs: { 'did:plc:fake-labeler': [ interpretLabelValueDefinition(customLabelDef, 'did:plc:fake-labeler'), ], }, } }, [label, visibility, noAdult, isLoggedOut, isTargetMe, did, customLabelDef]) const profileModeration = React.useMemo(() => { return moderateProfile(profile, modOpts) }, [profile, modOpts]) const postModeration = React.useMemo(() => { return moderatePost(post, modOpts) }, [post, modOpts]) return (

Moderation states

Label Block Mute {scenario[0] === 'label' && ( <> {LABEL_VALUES.map(labelValue => { let targetFixed = target[0] if ( targetFixed !== 'account' && targetFixed !== 'profile' ) { targetFixed = 'content' } const disabled = isSelfLabel && LABELS[labelValue].flags.includes('no-self') return ( {labelValue} ) })} Custom label {label[0] === 'custom' ? ( ) : ( <> )} Target is me Following target Self label Adult disabled Logged out {LABELS[label[0] as keyof typeof LABELS]?.configurable !== false && ( Preference Hide Warn Ignore )} Target Account Profile Post Embed )} Post Notifications Account Data {view[0] === 'post' && ( <> )} {view[0] === 'notifications' && ( <> )} {view[0] === 'account' && ( <> )} {view[0] === 'data' && ( <> )}
) } function Heading({title, subtitle}: {title: string; subtitle?: string}) { const t = useTheme() return (

{title}{' '} {!!subtitle && (

{subtitle}

)} ) } function CustomLabelForm({ def, setDef, }: { def: ComAtprotoLabelDefs.LabelValueDefinition setDef: React.Dispatch< React.SetStateAction > }) { const t = useTheme() return ( Blurs setDef(v => ({...v, blurs: values[0]}))}> Content Media None Severity setDef(v => ({...v, severity: values[0]}))}> Alert Inform None ) } function Toggler({label, children}: React.PropsWithChildren<{label: string}>) { const t = useTheme() const [show, setShow] = React.useState(false) return ( {show && children} ) } function SmallToggler({ label, children, }: React.PropsWithChildren<{label: string}>) { const [show, setShow] = React.useState(false) return ( {show && children} ) } function DataView({label, data}: {label: string; data: any}) { return ( {JSON.stringify(data, null, 2)} ) } function ModerationUIView({ mod, label, }: { mod: ModerationDecision label: string }) { return ( {[ 'profileList', 'profileView', 'avatar', 'banner', 'displayName', 'contentList', 'contentView', 'contentMedia', ].map(key => { const ui = mod.ui(key as keyof ModerationBehavior) return ( {key} ) })} ) } function Spacer() { return } function MockPostFeedItem({ post, moderation, }: { post: AppBskyFeedDefs.PostView moderation: ModerationDecision }) { const t = useTheme() if (moderation.ui('contentList').filter) { return (

Filtered from the feed

) } return ( ) } function MockPostThreadItem({ post, reply, }: { post: AppBskyFeedDefs.PostView moderation: ModerationDecision reply?: boolean }) { return ( {}} /> ) } function MockNotifItem({ notif, moderationOpts, }: { notif: FeedNotification moderationOpts: ModerationOpts }) { const t = useTheme() if (shouldFilterNotif(notif.notification, moderationOpts)) { return (

Filtered from the feed

) } return } function MockAccountCard({ profile, moderation, }: { profile: AppBskyActorDefs.ProfileViewBasic moderation: ModerationDecision }) { const t = useTheme() if (moderation.ui('profileList').filter) { return (

Filtered from the listing

) } return } function MockAccountScreen({ profile, moderation, moderationOpts, }: { profile: AppBskyActorDefs.ProfileViewBasic moderation: ModerationDecision moderationOpts: ModerationOpts }) { const t = useTheme() const {_} = useLingui() return ( ) } function Flag({v, label}: {v: boolean | undefined; label: string}) { const t = useTheme() return ( {v && }

{label}

) }