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 {useAgeAssurance} from '#/state/ageAssurance/useAgeAssurance'
import {
useMyLabelersQuery,
usePreferencesQuery,
type UsePreferencesQueryResponse,
usePreferencesSetAdultContentMutation,
} from '#/state/queries/preferences'
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
import {useSetMinimalShellMode} from '#/state/shell'
import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {AgeAssuranceAdmonition} from '#/components/ageAssurance/AgeAssuranceAdmonition'
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 {isReady: isAgeInfoReady} = useAgeAssurance()
const isLoading = isPreferencesLoading || !isAgeInfoReady
const error = preferencesError
return (
Moderation
{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()
const {declaredAge, isDeclaredUnderage, isAgeRestricted} = useAgeAssurance()
useFocusEffect(
useCallback(() => {
setMinimalShellMode(false)
}, [setMinimalShellMode]),
)
const {mutateAsync: setAdultContentPref, variables: optimisticAdultContent} =
usePreferencesSetAdultContentMutation()
const adultContentEnabled = !!(
(optimisticAdultContent && optimisticAdultContent.enabled) ||
(!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled)
)
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 (
{isDeclaredUnderage && (
Your declared age is under 18. Some settings below may be
disabled. If this was a mistake, you may edit your birthdate in
your{' '}
account settings
.
)}
Moderation tools
{state => (
)}
{state => (
)}
{state => (
)}
{state => (
)}
{state => (
)}
{declaredAge === undefined && (
<>
Content filters
>
)}
{!isDeclaredUnderage && (
<>
Content filters
You must complete age assurance in order to access the settings
below.
{!isDeclaredUnderage && (
<>
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
.
)}
{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,
) && }
)}
)
})}
)}
)
}