import {Fragment, useCallback} from 'react' import {Linking, View} from 'react-native' import {LABELS} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' import {getLabelingServiceTitle} from '#/lib/moderation' import { type CommonNavigatorParams, type NativeStackScreenProps, } from '#/lib/routes/types' import {logger} from '#/logger' import {isIOS} from '#/platform/detection' import { useMyLabelersQuery, usePreferencesQuery, type UsePreferencesQueryResponse, usePreferencesSetAdultContentMutation, } from '#/state/queries/preferences' import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities' import {useSetMinimalShellMode} from '#/state/shell' import {ViewHeader} from '#/view/com/util/ViewHeader' import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' import {Divider} from '#/components/Divider' import * as Toggle from '#/components/forms/Toggle' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck' import {type Props as SVGIconProps} from '#/components/icons/common' import {EditBig_Stroke2_Corner0_Rounded as EditBig} from '#/components/icons/EditBig' import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' import * as LabelingService from '#/components/LabelingServiceCard' import * as Layout from '#/components/Layout' import {InlineLinkText, Link} from '#/components/Link' import {ListMaybePlaceholder} from '#/components/Lists' import {Loader} from '#/components/Loader' import {GlobalLabelPreference} from '#/components/moderation/LabelPreference' import {Text} from '#/components/Typography' function ErrorState({error}: {error: string}) { const t = useTheme() return ( Hmmmm, it seems we're having trouble loading this data. See below for more details. If this issue persists, please contact us. {error} ) } export function ModerationScreen( _props: NativeStackScreenProps, ) { const {_} = useLingui() const { isLoading: isPreferencesLoading, error: preferencesError, data: preferences, } = usePreferencesQuery() const isLoading = isPreferencesLoading const error = preferencesError return ( {isLoading ? ( ) : error || !preferences ? ( ) : ( )} ) } function SubItem({ title, icon: Icon, style, }: ViewStyleProp & { title: string icon: React.ComponentType }) { const t = useTheme() return ( {title} ) } export function ModerationScreenInner({ preferences, }: { preferences: UsePreferencesQueryResponse }) { const {_} = useLingui() const t = useTheme() const setMinimalShellMode = useSetMinimalShellMode() const {gtMobile} = useBreakpoints() const {mutedWordsDialogControl} = useGlobalDialogsControlContext() const birthdateDialogControl = Dialog.useDialogControl() const { isLoading: isLabelersLoading, data: labelers, error: labelersError, } = useMyLabelersQuery() useFocusEffect( useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode]), ) const {mutateAsync: setAdultContentPref, variables: optimisticAdultContent} = usePreferencesSetAdultContentMutation() const adultContentEnabled = !!( (optimisticAdultContent && optimisticAdultContent.enabled) || (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled) ) const ageNotSet = !preferences.userAge const isUnderage = (preferences.userAge || 0) < 18 const onToggleAdultContentEnabled = useCallback( async (selected: boolean) => { try { await setAdultContentPref({ enabled: selected, }) } catch (e: any) { logger.error(`Failed to set adult content pref`, { message: e.message, }) } }, [setAdultContentPref], ) const disabledOnIOS = isIOS && !adultContentEnabled return ( Moderation tools {state => ( )} {state => ( )} {state => ( )} {state => ( )} {state => ( )} Content filters {ageNotSet && ( <> )} {!ageNotSet && !isUnderage && ( <> Enable adult content {adultContentEnabled ? ( Enabled ) : ( Disabled )} {disabledOnIOS && ( Adult content can only be enabled via the Web at{' '} { evt.preventDefault() Linking.openURL('https://bsky.app/') return false }}> bsky.app . )} )} {!isUnderage && adultContentEnabled && ( <> )} Advanced {isLabelersLoading ? ( ) : labelersError || !labelers ? ( We were unable to load your configured labelers at this time. ) : ( {labelers.map((labeler, i) => { return ( {i !== 0 && } {state => ( {isNonConfigurableModerationAuthority( labeler.creator.did, ) && } )} ) })} )} ) }