import React from 'react' import {View, Dimensions} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {AppBskyModerationDefs, LabelGroupDefinition} from '@atproto/api' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {atoms as a, useTheme, tokens, native} from '#/alf' import {Text} from '#/components/Typography' import * as Dialog from '#/components/Dialog' import {GlobalDialogProps} from '#/components/dialogs' import { Button, ButtonIcon, ButtonText, useButtonContext, } from '#/components/Button' import {Divider} from '#/components/Divider' import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings' import { ChevronRight_Stroke2_Corner0_Rounded as ChevronRight, ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft, } from '#/components/icons/Chevron' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import * as Toggle from '#/components/forms/Toggle' import {GradientFill} from '#/components/GradientFill' import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' import {Loader} from '#/components/Loader' import * as Toast from '#/view/com/util/Toast' import {usePreferencesQuery} from '#/state/queries/preferences' import {useModServicesDetailedInfoQuery} from '#/state/queries/modservice' import { getLabelGroupsFromLabels, getModerationServiceTitle, useConfigurableLabelGroups, } from '#/lib/moderation' export type ReportDialogProps = | { type: 'post' uri: string cid: string } | { type: 'user' did: string } function LabelGroupButton({ labelGroup, }: { labelGroup: LabelGroupDefinition['id'] }) { const t = useTheme() const {hovered, focused, pressed} = useButtonContext() const labelGroupStrings = useLabelGroupStrings() const groupInfoStrings = labelGroupStrings[labelGroup] const interacted = hovered || focused || pressed const styles = React.useMemo(() => { return { interacted: { backgroundColor: t.palette.contrast_50, }, } }, [t]) return ( {groupInfoStrings.name} {groupInfoStrings.description} ) } function ModServiceToggle({title}: {title: string}) { const t = useTheme() const ctx = Toggle.useItemContext() return ( {ctx.selected && } {title} ) } function SubmitViewLoader({ children, }: { children: (props: { labelers: AppBskyModerationDefs.ModServiceViewDetailed[] labelGroupToModServiceMap: Record }) => React.ReactNode }) { const { isLoading: isPreferencesLoading, error: preferencesError, data: preferences, } = usePreferencesQuery() const { isLoading: isModServicesLoading, data: modservices, error: modservicesError, } = useModServicesDetailedInfoQuery({ dids: preferences ? preferences.moderationOpts.mods.map(m => m.did) : [], }) const labelGroupToModServiceMap = React.useMemo(() => { if (!modservices) return {} const groups: Partial< Record< LabelGroupDefinition['id'], AppBskyModerationDefs.ModServiceViewDetailed[] > > = {} for (const modservice of modservices) { const labelGroups = getLabelGroupsFromLabels( modservice.policies.labelValues, ) for (const group of labelGroups) { const g = (groups[group.id] = groups[group.id] || []) g.push(modservice) } } return groups }, [modservices]) const isLoading = isPreferencesLoading || isModServicesLoading const error = preferencesError || modservicesError return isLoading ? ( ) : error || !(preferences && modservices) ? null : ( // TODO children({ labelers: modservices, // TODO mismatched types // @ts-ignore labelGroupToModServiceMap, }) ) } function SubmitView({ selectedLabelGroup, goBack, onSubmitComplete, labelGroupToModServiceMap, }: { selectedLabelGroup: LabelGroupDefinition['id'] goBack: () => void onSubmitComplete: () => void labelers: AppBskyModerationDefs.ModServiceViewDetailed[] labelGroupToModServiceMap: Record< LabelGroupDefinition['id'], AppBskyModerationDefs.ModServiceViewDetailed[] > }) { const t = useTheme() const {_} = useLingui() const labelGroupStrings = useLabelGroupStrings() const groupInfoStrings = labelGroupStrings[selectedLabelGroup] const [details, setDetails] = React.useState('') const [submitting, setSubmitting] = React.useState(false) const [selectedServices, setSelectedServices] = React.useState([]) const supportedLabelers = labelGroupToModServiceMap[selectedLabelGroup] const submit = React.useCallback(async () => { setSubmitting(true) await new Promise(resolve => setTimeout(resolve, 1000)) setSubmitting(false) Toast.show(`Thank you. Your report has been sent.`) onSubmitComplete() }, [onSubmitComplete]) return ( {groupInfoStrings.name} {groupInfoStrings.description} Select the moderation service(s) to report to {supportedLabelers ? ( {supportedLabelers.map(labeler => { const title = getModerationServiceTitle({ displayName: labeler.creator.displayName, handle: labeler.creator.handle, }) return ( ) })} ) : ( None of your subscribed labelers support this content type. )} Optionally provide additional information below: ) } /** * TODO copyright link out to DMCA * TODO add "other" option */ export function ReportDialog({ params, cleanup, }: GlobalDialogProps) { const t = useTheme() const {_} = useLingui() const insets = useSafeAreaInsets() const control = Dialog.useDialogControl() const [selectedLabelGroup, setSelectedLabelGroup] = React.useState< LabelGroupDefinition['id'] | undefined >() const labelGroupStrings = useLabelGroupStrings() // REQUIRED CLEANUP const onClose = React.useCallback(() => cleanup(), [cleanup]) const i18n = React.useMemo(() => { let title = _(msg`Report this post`) let description = _(msg`Why should this post be reviewed?`) if (params.type === 'user') { title = _(msg`Report this user`) description = _(msg`Why should this user be reviewed?`) } return { title, description, } }, [_, params.type]) const groups = useConfigurableLabelGroups() return ( {selectedLabelGroup ? ( {props => ( // TODO same types mismatch // @ts-ignore setSelectedLabelGroup(undefined)} onSubmitComplete={control.close} /> )} ) : ( {i18n.title} {i18n.description} {groups.map(def => { const groupStrings = labelGroupStrings[def.id] return ( ) })} )} ) }