import {useCallback, useEffect, useImperativeHandle, useMemo} from 'react' import {findNodeHandle, type ListRenderItemInfo, View} from 'react-native' import { type AppBskyLabelerDefs, type InterpretedLabelValueDefinition, interpretLabelValueDefinitions, type ModerationOpts, } from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation' import {isIOS, isNative} from '#/platform/detection' import {List, type ListRef} from '#/view/com/util/List' import {atoms as a, ios, tokens, useTheme} from '#/alf' import {Divider} from '#/components/Divider' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {ListFooter} from '#/components/Lists' import {Loader} from '#/components/Loader' import {LabelerLabelPreference} from '#/components/moderation/LabelPreference' import {Text} from '#/components/Typography' import {ErrorState} from '../ErrorState' import {type SectionRef} from './types' interface LabelsSectionProps { ref: React.Ref isLabelerLoading: boolean labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed | undefined labelerError: Error | null moderationOpts: ModerationOpts scrollElRef: ListRef headerHeight: number isFocused: boolean setScrollViewTag: (tag: number | null) => void } export function ProfileLabelsSection({ ref, isLabelerLoading, labelerInfo, labelerError, moderationOpts, scrollElRef, headerHeight, isFocused, setScrollViewTag, }: LabelsSectionProps) { const t = useTheme() const onScrollToTop = useCallback(() => { // @ts-expect-error TODO fix this scrollElRef.current?.scrollTo({ animated: isNative, x: 0, y: -headerHeight, }) }, [scrollElRef, headerHeight]) useImperativeHandle(ref, () => ({ scrollToTop: onScrollToTop, })) useEffect(() => { if (isIOS && isFocused && scrollElRef.current) { const nativeTag = findNodeHandle(scrollElRef.current) setScrollViewTag(nativeTag) } }, [isFocused, scrollElRef, setScrollViewTag]) const isSubscribed = labelerInfo ? !!isLabelerSubscribed(labelerInfo, moderationOpts) : false const labelValues = useMemo(() => { if (isLabelerLoading || !labelerInfo || labelerError) return [] const customDefs = interpretLabelValueDefinitions(labelerInfo) return labelerInfo.policies.labelValues .filter((val, i, arr) => arr.indexOf(val) === i) // dedupe .map(val => lookupLabelValueDefinition(val, customDefs)) .filter( def => def && def?.configurable, ) as InterpretedLabelValueDefinition[] }, [labelerInfo, labelerError, isLabelerLoading]) const numItems = labelValues.length const renderItem = useCallback( ({item, index}: ListRenderItemInfo) => { if (!labelerInfo) return null return ( {index !== 0 && } ) }, [labelerInfo, isSubscribed, numItems, t], ) return ( } ListFooterComponent={ } /> ) } function keyExtractor(item: InterpretedLabelValueDefinition) { return item.identifier } export function LabelerListHeader({ isLabelerLoading, labelerError, labelerInfo, hasValues, isSubscribed, }: { isLabelerLoading: boolean labelerError?: Error | null labelerInfo?: AppBskyLabelerDefs.LabelerViewDetailed hasValues: boolean isSubscribed: boolean }) { const t = useTheme() const {_} = useLingui() if (isLabelerLoading) { return ( ) } if (labelerError || !labelerInfo) { return ( ) } return ( Labels are annotations on users and content. They can be used to hide, warn, and categorize the network. {labelerInfo?.creator.viewer?.blocking ? ( Blocking does not prevent this labeler from placing labels on your account. ) : null} {!hasValues ? ( This labeler hasn't declared what labels it publishes, and may not be active. ) : !isSubscribed ? ( Subscribe to @{labelerInfo.creator.handle} to use these labels: ) : null} ) }