import React from 'react'
import {View} from 'react-native'
import {AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {
usePreferencesQuery,
useRemoveMutedWordMutation,
useUpsertMutedWordsMutation,
} from '#/state/queries/preferences'
import {
atoms as a,
native,
useBreakpoints,
useTheme,
ViewStyleProp,
web,
} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
import {Divider} from '#/components/Divider'
import * as Toggle from '#/components/forms/Toggle'
import {useFormatDistance} from '#/components/hooks/dates'
import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag'
import {PageText_Stroke2_Corner0_Rounded as PageText} from '#/components/icons/PageText'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
const ONE_DAY = 24 * 60 * 60 * 1000
export function MutedWordsDialog() {
const {mutedWordsDialogControl: control} = useGlobalDialogsControlContext()
return (
)
}
function MutedWordsInner() {
const t = useTheme()
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const {
isLoading: isPreferencesLoading,
data: preferences,
error: preferencesError,
} = usePreferencesQuery()
const {isPending, mutateAsync: addMutedWord} = useUpsertMutedWordsMutation()
const [field, setField] = React.useState('')
const [targets, setTargets] = React.useState(['content'])
const [error, setError] = React.useState('')
const [durations, setDurations] = React.useState(['forever'])
const [excludeFollowing, setExcludeFollowing] = React.useState(false)
const submit = React.useCallback(async () => {
const sanitizedValue = sanitizeMutedWordValue(field)
const surfaces = ['tag', targets.includes('content') && 'content'].filter(
Boolean,
) as AppBskyActorDefs.MutedWord['targets']
const actorTarget = excludeFollowing ? 'exclude-following' : 'all'
const now = Date.now()
const rawDuration = durations.at(0)
// undefined evaluates to 'forever'
let duration: string | undefined
if (rawDuration === '24_hours') {
duration = new Date(now + ONE_DAY).toISOString()
} else if (rawDuration === '7_days') {
duration = new Date(now + 7 * ONE_DAY).toISOString()
} else if (rawDuration === '30_days') {
duration = new Date(now + 30 * ONE_DAY).toISOString()
}
if (!sanitizedValue || !surfaces.length) {
setField('')
setError(_(msg`Please enter a valid word, tag, or phrase to mute`))
return
}
try {
// send raw value and rely on SDK as sanitization source of truth
await addMutedWord([
{
value: field,
targets: surfaces,
actorTarget,
expiresAt: duration,
},
])
setField('')
} catch (e: any) {
logger.error(`Failed to save muted word`, {message: e.message})
setError(e.message)
}
}, [_, field, targets, addMutedWord, setField, durations, excludeFollowing])
return (
Add muted words and tags
Posts can be muted based on their text, their tags, or both. We
recommend avoiding common words that appear in many posts, since it
can result in no posts being shown.
{
if (error) {
setError('')
}
setField(value)
}}
onSubmitEditing={submit}
/>
Duration:
Forever
24 hours
7 days
30 days
Mute in:
Text & tags
Tags only
Options:
Exclude users you follow
{error && (
{error}
)}
Your muted words
{isPreferencesLoading ? (
) : preferencesError || !preferences ? (
We're sorry, but we weren't able to load your muted words at
this time. Please try again.
) : preferences.moderationPrefs.mutedWords.length ? (
[...preferences.moderationPrefs.mutedWords]
.reverse()
.map((word, i) => (
))
) : (
You haven't muted any words or tags yet
)}
{isNative && }
)
}
function MutedWordRow({
style,
word,
}: ViewStyleProp & {word: AppBskyActorDefs.MutedWord}) {
const t = useTheme()
const {_} = useLingui()
const {isPending, mutateAsync: removeMutedWord} = useRemoveMutedWordMutation()
const control = Prompt.usePromptControl()
const expiryDate = word.expiresAt ? new Date(word.expiresAt) : undefined
const isExpired = expiryDate && expiryDate < new Date()
const formatDistance = useFormatDistance()
const remove = React.useCallback(async () => {
control.close()
removeMutedWord(word)
}, [removeMutedWord, word, control])
return (
<>
{word.targets.find(t => t === 'content') ? (
{word.value}{' '}
in{' '}
text & tags
) : (
{word.value}{' '}
in{' '}
tags
)}
{(expiryDate || word.actorTarget === 'exclude-following') && (
{expiryDate && (
<>
{isExpired ? (
Expired
) : (
Expires{' '}
{formatDistance(expiryDate, new Date(), {
addSuffix: true,
})}
)}
>
)}
{word.actorTarget === 'exclude-following' && (
<>
{' • '}
Excludes users you follow
>
)}
)}
>
)
}
function TargetToggle({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
const ctx = Toggle.useItemContext()
const {gtMobile} = useBreakpoints()
return (
{children}
)
}