Remove dead code and start moving toward latest modsdk
This commit is contained in:
@@ -2,25 +2,29 @@ import React from 'react'
|
|||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {LABEL_GROUPS} from '@atproto/api'
|
|
||||||
|
|
||||||
import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings'
|
import {useLabelStrings} from '#/lib/moderation/useLabelStrings'
|
||||||
|
|
||||||
import {useTheme, atoms as a} from '#/alf'
|
import {useTheme, atoms as a} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||||
|
|
||||||
export function ModerationLabelPref({
|
export function ModerationLabelPref({
|
||||||
labelGroup,
|
label,
|
||||||
disabled,
|
disabled,
|
||||||
}: {
|
}: {
|
||||||
labelGroup: keyof typeof LABEL_GROUPS
|
label: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const labelGroupStrings = useLabelGroupStrings()
|
const allLabelStrings = useLabelStrings()
|
||||||
const groupInfoStrings = labelGroupStrings[labelGroup]
|
const labelStrings = allLabelStrings[label] || {
|
||||||
|
general: {
|
||||||
|
name: label,
|
||||||
|
description: `Labeled "${label}"`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// TODO add onChange behavior when mod prefs are updated
|
// TODO add onChange behavior when mod prefs are updated
|
||||||
|
|
||||||
@@ -42,16 +46,16 @@ export function ModerationLabelPref({
|
|||||||
a.align_center,
|
a.align_center,
|
||||||
]}>
|
]}>
|
||||||
<View style={[a.gap_xs, {width: '50%'}]}>
|
<View style={[a.gap_xs, {width: '50%'}]}>
|
||||||
<Text style={[a.font_bold]}>{groupInfoStrings.name}</Text>
|
<Text style={[a.font_bold]}>{labelStrings.general.name}</Text>
|
||||||
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
|
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
|
||||||
{groupInfoStrings.description}
|
{labelStrings.general.description}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[a.justify_center, {minHeight: 35}]}>
|
<View style={[a.justify_center, {minHeight: 35}]}>
|
||||||
{!disabled && (
|
{!disabled && (
|
||||||
<ToggleButton.Group
|
<ToggleButton.Group
|
||||||
label={_(
|
label={_(
|
||||||
msg`Configure content filtering setting for category: ${groupInfoStrings.name.toLowerCase()}`,
|
msg`Configure content filtering setting for category: ${labelStrings.general.name.toLowerCase()}`,
|
||||||
)}
|
)}
|
||||||
values={['hide']}
|
values={['hide']}
|
||||||
onChange={() => {}}>
|
onChange={() => {}}>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import {
|
|||||||
getModerationServiceTitle,
|
getModerationServiceTitle,
|
||||||
useConfigurableContentLabelGroups,
|
useConfigurableContentLabelGroups,
|
||||||
useConfigurableProfileLabelGroups,
|
useConfigurableProfileLabelGroups,
|
||||||
getLabelGroupToLabelerMap,
|
|
||||||
} from '#/lib/moderation'
|
} from '#/lib/moderation'
|
||||||
import {DMCA_LINK} from '#/components/ReportDialog/const'
|
import {DMCA_LINK} from '#/components/ReportDialog/const'
|
||||||
import {Link} from '#/components/Link'
|
import {Link} from '#/components/Link'
|
||||||
@@ -165,12 +164,10 @@ function SubmitView({
|
|||||||
selectedLabelGroup,
|
selectedLabelGroup,
|
||||||
goBack,
|
goBack,
|
||||||
onSubmitComplete,
|
onSubmitComplete,
|
||||||
labelGroupToLabelerMap,
|
|
||||||
}: ReportDialogProps & {
|
}: ReportDialogProps & {
|
||||||
selectedLabelGroup: ReportDialogLabelIds
|
selectedLabelGroup: ReportDialogLabelIds
|
||||||
goBack: () => void
|
goBack: () => void
|
||||||
onSubmitComplete: () => void
|
onSubmitComplete: () => void
|
||||||
labelGroupToLabelerMap: ReturnType<typeof getLabelGroupToLabelerMap>
|
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
@@ -178,7 +175,7 @@ function SubmitView({
|
|||||||
const groupInfoStrings = labelGroupStrings[selectedLabelGroup]
|
const groupInfoStrings = labelGroupStrings[selectedLabelGroup]
|
||||||
const [details, setDetails] = React.useState<string>('')
|
const [details, setDetails] = React.useState<string>('')
|
||||||
const [submitting, setSubmitting] = React.useState<boolean>(false)
|
const [submitting, setSubmitting] = React.useState<boolean>(false)
|
||||||
const supportedLabelers = labelGroupToLabelerMap[selectedLabelGroup]
|
const supportedLabelers = [] //labelGroupToLabelerMap[selectedLabelGroup]
|
||||||
const [selectedServices, setSelectedServices] = React.useState<string[]>(
|
const [selectedServices, setSelectedServices] = React.useState<string[]>(
|
||||||
supportedLabelers?.map(labeler => labeler.creator.did) || [],
|
supportedLabelers?.map(labeler => labeler.creator.did) || [],
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-71
@@ -2,8 +2,6 @@ import React from 'react'
|
|||||||
import {
|
import {
|
||||||
ModerationCause,
|
ModerationCause,
|
||||||
ModerationUI,
|
ModerationUI,
|
||||||
LABEL_GROUPS,
|
|
||||||
LabelGroupDefinition,
|
|
||||||
AppBskyModerationDefs,
|
AppBskyModerationDefs,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
|
||||||
@@ -27,54 +25,19 @@ export function isJustAMute(modui: ModerationUI): boolean {
|
|||||||
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
|
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLabelGroupsFromLabels(labels: string[]) {
|
|
||||||
const groups: LabelGroupDefinition[] = []
|
|
||||||
|
|
||||||
for (const label of labels) {
|
|
||||||
for (const group in LABEL_GROUPS) {
|
|
||||||
const def = LABEL_GROUPS[group as LabelGroupDefinition['id']]
|
|
||||||
if (def.labels.find(l => l.id === label)) {
|
|
||||||
groups.push(def)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getConfigurableLabelGroups() {
|
|
||||||
return Object.values(LABEL_GROUPS).filter(group => group.configurable)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useConfigurableLabelGroups() {
|
|
||||||
return React.useMemo(() => getConfigurableLabelGroups(), [])
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useConfigurableContentLabelGroups() {
|
export function useConfigurableContentLabelGroups() {
|
||||||
return React.useMemo(() => {
|
// TODO removeme
|
||||||
const groups = getConfigurableLabelGroups()
|
return []
|
||||||
return groups.filter(group => {
|
|
||||||
return group.labels.every(l => l.targets.includes('content'))
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useConfigurableProfileLabelGroups() {
|
export function useConfigurableProfileLabelGroups() {
|
||||||
return React.useMemo(() => {
|
// TODO removeme
|
||||||
const groups = getConfigurableLabelGroups()
|
return []
|
||||||
return groups.filter(group => {
|
|
||||||
return group.labels.every(l => l.targets.includes('profile'))
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useConfigurableAccountLabelGroups() {
|
export function useConfigurableAccountLabelGroups() {
|
||||||
return React.useMemo(() => {
|
// TODO removeme
|
||||||
const groups = getConfigurableLabelGroups()
|
return []
|
||||||
return groups.filter(group => {
|
|
||||||
return group.labels.every(l => l.targets.includes('account'))
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getModerationServiceTitle({
|
export function getModerationServiceTitle({
|
||||||
@@ -88,31 +51,3 @@ export function getModerationServiceTitle({
|
|||||||
? sanitizeDisplayName(displayName)
|
? sanitizeDisplayName(displayName)
|
||||||
: sanitizeHandle(handle, '@')
|
: sanitizeHandle(handle, '@')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLabelGroupToLabelerMap(
|
|
||||||
labelers: AppBskyModerationDefs.ModServiceViewDetailed[],
|
|
||||||
) {
|
|
||||||
if (!labelers) return {}
|
|
||||||
|
|
||||||
const groups: Partial<
|
|
||||||
Record<
|
|
||||||
LabelGroupDefinition['id'] | 'other',
|
|
||||||
AppBskyModerationDefs.ModServiceViewDetailed[]
|
|
||||||
>
|
|
||||||
> = {
|
|
||||||
// `other` reports go to all labelers TODO confirm this
|
|
||||||
other: labelers,
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const modservice of labelers) {
|
|
||||||
const labelGroups = getLabelGroupsFromLabels(
|
|
||||||
modservice.policies.labelValues,
|
|
||||||
)
|
|
||||||
for (const group of labelGroups) {
|
|
||||||
const g = (groups[group.id] = groups[group.id] || [])
|
|
||||||
g.push(modservice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import {LABELS} from '@atproto/api'
|
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
|
|
||||||
export type LabelStrings = Record<
|
export type LabelStrings = Record<
|
||||||
keyof typeof LABELS,
|
string,
|
||||||
{
|
{
|
||||||
general: {name: string; description: string}
|
general: {name: string; description: string}
|
||||||
account: {name: string; description: string}
|
account: {name: string; description: string}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {ModerationCause} from '@atproto/api'
|
import {ModerationCause, LABELS} from '@atproto/api'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useLabelStrings} from './useLabelStrings'
|
import {useLabelStrings} from './useLabelStrings'
|
||||||
@@ -76,8 +76,9 @@ export function useModerationCauseDescription(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cause.type === 'label') {
|
if (cause.type === 'label') {
|
||||||
if (cause.labelDef.id in labelStrings) {
|
if (cause.labelDef.identifier in labelStrings) {
|
||||||
const strings = labelStrings[cause.labelDef.id]
|
const strings =
|
||||||
|
labelStrings[cause.labelDef.identifier as keyof typeof LABELS]
|
||||||
return {
|
return {
|
||||||
name:
|
name:
|
||||||
context === 'account' ? strings.account.name : strings.content.name,
|
context === 'account' ? strings.account.name : strings.content.name,
|
||||||
@@ -88,8 +89,8 @@ export function useModerationCauseDescription(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name: cause.labelDef.id,
|
name: cause.labelDef.identifier,
|
||||||
description: _(msg`Labeled ${cause.labelDef.id}`),
|
description: _(msg`Labeled ${cause.labelDef.identifier}`),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// should never happen
|
// should never happen
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function ModerationScreen(
|
|||||||
data: modservices,
|
data: modservices,
|
||||||
error: modservicesError,
|
error: modservicesError,
|
||||||
} = useModServicesDetailedInfoQuery({
|
} = useModServicesDetailedInfoQuery({
|
||||||
dids: preferences ? preferences.moderationOpts.mods.map(m => m.did) : [],
|
dids: preferences ? preferences.moderationPrefs.mods.map(m => m.did) : [],
|
||||||
})
|
})
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {height} = useSafeAreaFrame()
|
const {height} = useSafeAreaFrame()
|
||||||
@@ -149,7 +149,7 @@ export function ModerationScreenInner({
|
|||||||
usePreferencesSetAdultContentMutation()
|
usePreferencesSetAdultContentMutation()
|
||||||
const adultContentEnabled = !!(
|
const adultContentEnabled = !!(
|
||||||
(optimisticAdultContent && optimisticAdultContent.enabled) ||
|
(optimisticAdultContent && optimisticAdultContent.enabled) ||
|
||||||
(!optimisticAdultContent && preferences.moderationOpts.adultContentEnabled)
|
(!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled)
|
||||||
)
|
)
|
||||||
|
|
||||||
const onToggleAdultContentEnabled = React.useCallback(
|
const onToggleAdultContentEnabled = React.useCallback(
|
||||||
@@ -323,7 +323,9 @@ export function ModerationScreenInner({
|
|||||||
modservice={{
|
modservice={{
|
||||||
uri: '',
|
uri: '',
|
||||||
cid: '',
|
cid: '',
|
||||||
policies: {},
|
policies: {
|
||||||
|
labelValues: [],
|
||||||
|
},
|
||||||
creator: {
|
creator: {
|
||||||
did: '',
|
did: '',
|
||||||
handle: 'safety.bsky.app',
|
handle: 'safety.bsky.app',
|
||||||
@@ -360,7 +362,7 @@ export function ModerationScreenInner({
|
|||||||
handle: mod.creator.handle,
|
handle: mod.creator.handle,
|
||||||
})}
|
})}
|
||||||
handle={mod.creator.handle}
|
handle={mod.creator.handle}
|
||||||
description={mod.description}
|
description={mod.creator.description || ''}
|
||||||
/>
|
/>
|
||||||
</ModerationServiceCard.Card.Outer>
|
</ModerationServiceCard.Card.Outer>
|
||||||
</ModerationServiceCard.Link>
|
</ModerationServiceCard.Link>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ let ProfileHeaderModerator = ({
|
|||||||
useModServiceSubscriptionMutation()
|
useModServiceSubscriptionMutation()
|
||||||
const isSubscribed =
|
const isSubscribed =
|
||||||
variables?.subscribe ??
|
variables?.subscribe ??
|
||||||
preferences?.moderationOpts.mods.find(mod => mod.did === profile.did)
|
preferences?.moderationPrefs.mods.find(mod => mod.did === profile.did)
|
||||||
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
|
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
|
||||||
const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
|
const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
|
||||||
useUnlikeMutation()
|
useUnlikeMutation()
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {useSafeAreaFrame} from 'react-native-safe-area-context'
|
import {useSafeAreaFrame} from 'react-native-safe-area-context'
|
||||||
|
|
||||||
import {useModServiceSubscriptionMutation} from '#/state/queries/modservice'
|
import {useModServiceSubscriptionMutation} from '#/state/queries/modservice'
|
||||||
import {getLabelGroupsFromLabels} from '#/lib/moderation'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
|
||||||
import {useTheme, atoms as a} from '#/alf'
|
import {useTheme, atoms as a} from '#/alf'
|
||||||
@@ -76,15 +75,8 @@ export function ProfileContentFiltersSectionInner({
|
|||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const groups = React.useMemo(() => {
|
|
||||||
return getLabelGroupsFromLabels(modservice.policies.labelValues).filter(
|
|
||||||
def => def.configurable,
|
|
||||||
)
|
|
||||||
}, [modservice.policies.labelValues])
|
|
||||||
const isEnabled = Boolean(
|
const isEnabled = Boolean(
|
||||||
moderationOpts.mods.find(
|
moderationOpts.prefs.mods.find(mod => mod.did === modservice.creator.did),
|
||||||
mod => mod.did === modservice.creator.did && mod.enabled,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
const hasSession = true // TODO
|
const hasSession = true // TODO
|
||||||
|
|
||||||
@@ -92,7 +84,7 @@ export function ProfileContentFiltersSectionInner({
|
|||||||
useModServiceSubscriptionMutation()
|
useModServiceSubscriptionMutation()
|
||||||
const isSubscribed =
|
const isSubscribed =
|
||||||
variables?.subscribe ??
|
variables?.subscribe ??
|
||||||
moderationOpts.mods.find(mod => mod.did === modservice.creator.did)
|
moderationOpts.prefs.mods.find(mod => mod.did === modservice.creator.did)
|
||||||
|
|
||||||
const onPressSubscribe = React.useCallback(async () => {
|
const onPressSubscribe = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -142,7 +134,8 @@ export function ProfileContentFiltersSectionInner({
|
|||||||
a.border,
|
a.border,
|
||||||
t.atoms.border_contrast_low,
|
t.atoms.border_contrast_low,
|
||||||
]}>
|
]}>
|
||||||
{groups.map((def, i) => {
|
{
|
||||||
|
undefined /* TODO modservice.policies.labelValues.map((def, i) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={def.id}>
|
<React.Fragment key={def.id}>
|
||||||
{i !== 0 && <Divider />}
|
{i !== 0 && <Divider />}
|
||||||
@@ -152,7 +145,8 @@ export function ProfileContentFiltersSectionInner({
|
|||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})*/
|
||||||
|
}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{height: 100}} />
|
<View style={{height: 100}} />
|
||||||
|
|||||||
@@ -119,10 +119,6 @@ export interface AddAppPasswordModal {
|
|||||||
name: 'add-app-password'
|
name: 'add-app-password'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContentFilteringSettingsModal {
|
|
||||||
name: 'content-filtering-settings'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContentLanguagesSettingsModal {
|
export interface ContentLanguagesSettingsModal {
|
||||||
name: 'content-languages-settings'
|
name: 'content-languages-settings'
|
||||||
}
|
}
|
||||||
@@ -182,7 +178,6 @@ export type Modal =
|
|||||||
| SwitchAccountModal
|
| SwitchAccountModal
|
||||||
|
|
||||||
// Curation
|
// Curation
|
||||||
| ContentFilteringSettingsModal
|
|
||||||
| ContentLanguagesSettingsModal
|
| ContentLanguagesSettingsModal
|
||||||
| PostLanguagesSettingsModal
|
| PostLanguagesSettingsModal
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import {DEFAULT_LOGGED_OUT_PREFERENCES, useModerationOpts} from './preferences'
|
|||||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||||
import {isJustAMute} from '#/lib/moderation'
|
import {isJustAMute} from '#/lib/moderation'
|
||||||
|
|
||||||
const DEFAULT_MOD_OPTS = DEFAULT_LOGGED_OUT_PREFERENCES.moderationOpts
|
const DEFAULT_MOD_OPTS = {
|
||||||
|
userDid: undefined,
|
||||||
|
prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
|
||||||
|
}
|
||||||
|
|
||||||
export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix]
|
export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix]
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ export function useModServiceInfoQuery({
|
|||||||
enabled: !!did && enabled !== false,
|
enabled: !!did && enabled !== false,
|
||||||
queryKey: modServiceInfoQueryKey(did as string),
|
queryKey: modServiceInfoQueryKey(did as string),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await getAgent().app.bsky.moderation.getService({
|
const res = await getAgent().app.bsky.moderation.getServices({
|
||||||
did: did as string,
|
dids: [did as string],
|
||||||
|
detailed: true,
|
||||||
})
|
})
|
||||||
return res.data
|
return res.data.views[0] as AppBskyModerationDefs.ModServiceViewDetailed
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -40,7 +41,7 @@ export function useModServicesInfoQuery({dids}: {dids: string[]}) {
|
|||||||
queryKey: modServicesInfoQueryKey(dids),
|
queryKey: modServicesInfoQueryKey(dids),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await getAgent().app.bsky.moderation.getServices({dids})
|
const res = await getAgent().app.bsky.moderation.getServices({dids})
|
||||||
return res.data.views
|
return res.data.views as AppBskyModerationDefs.ModServiceView[]
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -50,23 +51,11 @@ export function useModServicesDetailedInfoQuery({dids}: {dids: string[]}) {
|
|||||||
enabled: !!dids.length,
|
enabled: !!dids.length,
|
||||||
queryKey: modServicesDetailedInfoQueryKey(dids),
|
queryKey: modServicesDetailedInfoQueryKey(dids),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const views: AppBskyModerationDefs.ModServiceViewDetailed[] = []
|
const res = await getAgent().app.bsky.moderation.getServices({
|
||||||
|
dids,
|
||||||
await Promise.all(
|
detailed: true,
|
||||||
dids.map(did => {
|
})
|
||||||
return getAgent()
|
return res.data.views as AppBskyModerationDefs.ModServiceViewDetailed[]
|
||||||
.app.bsky.moderation.getService({did})
|
|
||||||
.then(res => {
|
|
||||||
views.push(res.data)
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.error(e)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
return views
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -95,50 +84,3 @@ export function useModServiceSubscriptionMutation() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useModServiceEnableMutation() {
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
async mutationFn({did, enabled}: {did: string; enabled: boolean}) {
|
|
||||||
// TODO
|
|
||||||
z.object({
|
|
||||||
did: z.string(),
|
|
||||||
enabled: z.boolean(),
|
|
||||||
}).parse({did, enabled})
|
|
||||||
await getAgent().setModServiceEnabled(did, enabled)
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: preferencesQueryKey,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useModServiceLabelGroupEnableMutation() {
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
async mutationFn({
|
|
||||||
did,
|
|
||||||
group,
|
|
||||||
enabled,
|
|
||||||
}: {
|
|
||||||
did: string
|
|
||||||
group: string
|
|
||||||
enabled: boolean
|
|
||||||
}) {
|
|
||||||
// TODO
|
|
||||||
z.object({
|
|
||||||
did: z.string(),
|
|
||||||
group: z.string(),
|
|
||||||
enabled: z.boolean(),
|
|
||||||
}).parse({did, group, enabled})
|
|
||||||
await getAgent().setModServiceLabelGroupEnabled(did, group, enabled)
|
|
||||||
},
|
|
||||||
onSuccess() {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: preferencesQueryKey,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -423,10 +423,10 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
|
|||||||
let somePostsPassModeration = false
|
let somePostsPassModeration = false
|
||||||
|
|
||||||
for (const item of feed) {
|
for (const item of feed) {
|
||||||
const moderation = moderatePost(
|
const moderation = moderatePost(item.post, {
|
||||||
item.post,
|
userDid: undefined,
|
||||||
DEFAULT_LOGGED_OUT_PREFERENCES.moderationOpts,
|
prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
|
||||||
)
|
})
|
||||||
|
|
||||||
if (!moderation.ui('contentList').filter) {
|
if (!moderation.ui('contentList').filter) {
|
||||||
// we have a sfw post
|
// we have a sfw post
|
||||||
|
|||||||
@@ -34,14 +34,15 @@ export const DEFAULT_LOGGED_OUT_PREFERENCES: UsePreferencesQueryResponse = {
|
|||||||
pinned: [],
|
pinned: [],
|
||||||
unpinned: [],
|
unpinned: [],
|
||||||
},
|
},
|
||||||
moderationOpts: {
|
moderationPrefs: {
|
||||||
userDid: '',
|
|
||||||
adultContentEnabled: false,
|
adultContentEnabled: false,
|
||||||
labelGroups: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
|
labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
|
||||||
mods: [],
|
mods: [],
|
||||||
},
|
},
|
||||||
feedViewPrefs: DEFAULT_HOME_FEED_PREFS,
|
feedViewPrefs: DEFAULT_HOME_FEED_PREFS,
|
||||||
threadViewPrefs: DEFAULT_THREAD_VIEW_PREFS,
|
threadViewPrefs: DEFAULT_THREAD_VIEW_PREFS,
|
||||||
userAge: 13, // TODO(pwi)
|
userAge: 13, // TODO(pwi)
|
||||||
interests: {tags: []},
|
interests: {tags: []},
|
||||||
|
mutedWords: [],
|
||||||
|
hiddenPosts: [],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
LabelPreference,
|
LabelPreference,
|
||||||
BskyFeedViewPreference,
|
BskyFeedViewPreference,
|
||||||
ModerationOpts,
|
ModerationOpts,
|
||||||
LabelGroupDefinition,
|
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
|
||||||
import {track} from '#/lib/analytics/analytics'
|
import {track} from '#/lib/analytics/analytics'
|
||||||
@@ -77,17 +76,20 @@ export function useModerationOpts() {
|
|||||||
const override = useContext(moderationOptsOverrideContext)
|
const override = useContext(moderationOptsOverrideContext)
|
||||||
const prefs = usePreferencesQuery()
|
const prefs = usePreferencesQuery()
|
||||||
const hiddenPosts = useHiddenPosts()
|
const hiddenPosts = useHiddenPosts()
|
||||||
const opts = useMemo(() => {
|
const opts = useMemo<ModerationOpts | undefined>(() => {
|
||||||
if (override) {
|
if (override) {
|
||||||
return override
|
return override
|
||||||
}
|
}
|
||||||
if (!prefs.data) {
|
if (!prefs.data) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const moderationOpts = prefs.data.moderationOpts
|
const moderationPrefs = prefs.data.moderationPrefs
|
||||||
return {
|
return {
|
||||||
...moderationOpts,
|
userDid: '', // TODO
|
||||||
hiddenPosts,
|
prefs: {
|
||||||
|
...moderationPrefs,
|
||||||
|
hiddenPosts,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}, [override, prefs.data, hiddenPosts])
|
}, [override, prefs.data, hiddenPosts])
|
||||||
return opts
|
return opts
|
||||||
@@ -130,13 +132,15 @@ export function useSetContentLabelMutation() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
labelGroup,
|
label,
|
||||||
visibility,
|
visibility,
|
||||||
|
labelerDid,
|
||||||
}: {
|
}: {
|
||||||
labelGroup: LabelGroupDefinition['id']
|
label: string
|
||||||
visibility: LabelPreference
|
visibility: LabelPreference
|
||||||
|
labelerDid?: string
|
||||||
}) => {
|
}) => {
|
||||||
await getAgent().setContentLabelPref(labelGroup, visibility)
|
await getAgent().setContentLabelPref(label, visibility, labelerDid)
|
||||||
// triggers a refetch
|
// triggers a refetch
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: preferencesQueryKey,
|
queryKey: preferencesQueryKey,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {ComAtprotoLabelDefs, DEFAULT_LABEL_GROUP_SETTINGS} from '@atproto/api'
|
import {ComAtprotoLabelDefs, DEFAULT_LABEL_SETTINGS} from '@atproto/api'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LabelGroup,
|
LabelGroup,
|
||||||
@@ -21,12 +21,9 @@ export type LabelGroupConfig = {
|
|||||||
*
|
*
|
||||||
* TODO(pwi)
|
* TODO(pwi)
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_GROUP_SETTINGS =
|
export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_SETTINGS =
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(DEFAULT_LABEL_GROUP_SETTINGS).map(([key, _pref]) => [
|
Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']),
|
||||||
key,
|
|
||||||
'hide',
|
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const CONFIGURABLE_LABEL_GROUPS: Record<
|
export const CONFIGURABLE_LABEL_GROUPS: Record<
|
||||||
|
|||||||
@@ -1,406 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {LabelPreference} from '@atproto/api'
|
|
||||||
import {StyleSheet, Pressable, View, Linking} from 'react-native'
|
|
||||||
import LinearGradient from 'react-native-linear-gradient'
|
|
||||||
import {ScrollView} from './util'
|
|
||||||
import {s, colors, gradients} from 'lib/styles'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {TextLink} from '../util/Link'
|
|
||||||
import {ToggleButton} from '../util/forms/ToggleButton'
|
|
||||||
import {Button} from '../util/forms/Button'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {isIOS} from 'platform/detection'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import * as Toast from '../util/Toast'
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {
|
|
||||||
usePreferencesQuery,
|
|
||||||
usePreferencesSetContentLabelMutation,
|
|
||||||
usePreferencesSetAdultContentMutation,
|
|
||||||
ConfigurableLabelGroup,
|
|
||||||
CONFIGURABLE_LABEL_GROUPS,
|
|
||||||
UsePreferencesQueryResponse,
|
|
||||||
} from '#/state/queries/preferences'
|
|
||||||
|
|
||||||
export const snapPoints = ['90%']
|
|
||||||
|
|
||||||
export function Component({}: {}) {
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {closeModal} = useModalControls()
|
|
||||||
const {data: preferences} = usePreferencesQuery()
|
|
||||||
|
|
||||||
const onPressDone = React.useCallback(() => {
|
|
||||||
closeModal()
|
|
||||||
}, [closeModal])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View testID="contentFilteringModal" style={[pal.view, styles.container]}>
|
|
||||||
<Text style={[pal.text, styles.title]}>
|
|
||||||
<Trans>Content Filtering</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<ScrollView style={styles.scrollContainer}>
|
|
||||||
<AdultContentEnabledPref />
|
|
||||||
<ContentLabelPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="nsfw"
|
|
||||||
disabled={!preferences?.moderationOpts.adultContentEnabled}
|
|
||||||
/>
|
|
||||||
<ContentLabelPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="nudity"
|
|
||||||
disabled={!preferences?.moderationOpts.adultContentEnabled}
|
|
||||||
/>
|
|
||||||
<ContentLabelPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="suggestive"
|
|
||||||
disabled={!preferences?.moderationOpts.adultContentEnabled}
|
|
||||||
/>
|
|
||||||
<ContentLabelPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="gore"
|
|
||||||
disabled={!preferences?.moderationOpts.adultContentEnabled}
|
|
||||||
/>
|
|
||||||
<ContentLabelPref preferences={preferences} labelGroup="hate" />
|
|
||||||
<ContentLabelPref preferences={preferences} labelGroup="spam" />
|
|
||||||
<ContentLabelPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="impersonation"
|
|
||||||
/>
|
|
||||||
<View style={{height: isMobile ? 60 : 0}} />
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.btnContainer,
|
|
||||||
isMobile && styles.btnContainerMobile,
|
|
||||||
pal.borderDark,
|
|
||||||
]}>
|
|
||||||
<Pressable
|
|
||||||
testID="sendReportBtn"
|
|
||||||
onPress={onPressDone}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Done`)}
|
|
||||||
accessibilityHint="">
|
|
||||||
<LinearGradient
|
|
||||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={[styles.btn]}>
|
|
||||||
<Text style={[s.white, s.bold, s.f18]}>
|
|
||||||
<Trans>Done</Trans>
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AdultContentEnabledPref() {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {data: preferences} = usePreferencesQuery()
|
|
||||||
const {mutate, variables} = usePreferencesSetAdultContentMutation()
|
|
||||||
const {openModal} = useModalControls()
|
|
||||||
|
|
||||||
const onSetAge = React.useCallback(
|
|
||||||
() => openModal({name: 'birth-date-settings'}),
|
|
||||||
[openModal],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onToggleAdultContent = React.useCallback(async () => {
|
|
||||||
if (isIOS) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
mutate({
|
|
||||||
enabled: !(
|
|
||||||
variables?.enabled ?? preferences?.moderationOpts.adultContentEnabled
|
|
||||||
),
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
Toast.show(
|
|
||||||
_(msg`There was an issue syncing your preferences with the server`),
|
|
||||||
)
|
|
||||||
logger.error('Failed to update preferences with server', {message: e})
|
|
||||||
}
|
|
||||||
}, [variables, preferences, mutate, _])
|
|
||||||
|
|
||||||
const onAdultContentLinkPress = React.useCallback(() => {
|
|
||||||
Linking.openURL('https://bsky.app/')
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={s.mb10}>
|
|
||||||
{isIOS ? (
|
|
||||||
preferences?.moderationOpts.adultContentEnabled ? null : (
|
|
||||||
<Text type="md" style={pal.textLight}>
|
|
||||||
<Trans>
|
|
||||||
Adult content can only be enabled via the Web at{' '}
|
|
||||||
<TextLink
|
|
||||||
style={pal.link}
|
|
||||||
href=""
|
|
||||||
text="bsky.app"
|
|
||||||
onPress={onAdultContentLinkPress}
|
|
||||||
/>
|
|
||||||
.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
) : typeof preferences?.birthDate === 'undefined' ? (
|
|
||||||
<View style={[pal.viewLight, styles.agePrompt]}>
|
|
||||||
<Text type="md" style={[pal.text, {flex: 1}]}>
|
|
||||||
<Trans>Confirm your age to enable adult content.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
label={_(msg({message: 'Set Age', context: 'action'}))}
|
|
||||||
onPress={onSetAge}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
) : (preferences.userAge || 0) >= 18 ? (
|
|
||||||
<ToggleButton
|
|
||||||
type="default-light"
|
|
||||||
label={_(msg`Enable Adult Content`)}
|
|
||||||
isSelected={
|
|
||||||
variables?.enabled ??
|
|
||||||
preferences?.moderationOpts.adultContentEnabled
|
|
||||||
}
|
|
||||||
onPress={onToggleAdultContent}
|
|
||||||
style={styles.toggleBtn}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<View style={[pal.viewLight, styles.agePrompt]}>
|
|
||||||
<Text type="md" style={[pal.text, {flex: 1}]}>
|
|
||||||
<Trans>You must be 18 or older to enable adult content.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
label={_(msg({message: 'Set Age', context: 'action'}))}
|
|
||||||
onPress={onSetAge}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Refactor this component to pass labels down to each tab
|
|
||||||
function ContentLabelPref({
|
|
||||||
preferences,
|
|
||||||
labelGroup,
|
|
||||||
disabled,
|
|
||||||
}: {
|
|
||||||
preferences?: UsePreferencesQueryResponse
|
|
||||||
labelGroup: ConfigurableLabelGroup
|
|
||||||
disabled?: boolean
|
|
||||||
}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const visibility = preferences?.moderationOpts.labelGroups?.[labelGroup]
|
|
||||||
const {mutate, variables} = usePreferencesSetContentLabelMutation()
|
|
||||||
|
|
||||||
const onChange = React.useCallback(
|
|
||||||
(vis: LabelPreference) => {
|
|
||||||
mutate({labelGroup, visibility: vis})
|
|
||||||
},
|
|
||||||
[mutate, labelGroup],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[styles.contentLabelPref, pal.border]}>
|
|
||||||
<View style={s.flex1}>
|
|
||||||
<Text type="md-medium" style={[pal.text]}>
|
|
||||||
{CONFIGURABLE_LABEL_GROUPS[labelGroup].title}
|
|
||||||
</Text>
|
|
||||||
{typeof CONFIGURABLE_LABEL_GROUPS[labelGroup].subtitle === 'string' && (
|
|
||||||
<Text type="sm" style={[pal.textLight]}>
|
|
||||||
{CONFIGURABLE_LABEL_GROUPS[labelGroup].subtitle}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{disabled || !visibility ? (
|
|
||||||
<Text type="sm-bold" style={pal.textLight}>
|
|
||||||
<Trans context="action">Hide</Trans>
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<SelectGroup
|
|
||||||
current={variables?.visibility || visibility}
|
|
||||||
onChange={onChange}
|
|
||||||
labelGroup={labelGroup}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectGroupProps {
|
|
||||||
current: LabelPreference
|
|
||||||
onChange: (v: LabelPreference) => void
|
|
||||||
labelGroup: ConfigurableLabelGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectGroup({current, onChange, labelGroup}: SelectGroupProps) {
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.selectableBtns}>
|
|
||||||
<SelectableBtn
|
|
||||||
current={current}
|
|
||||||
value="hide"
|
|
||||||
label={_(msg`Hide`)}
|
|
||||||
left
|
|
||||||
onChange={onChange}
|
|
||||||
labelGroup={labelGroup}
|
|
||||||
/>
|
|
||||||
<SelectableBtn
|
|
||||||
current={current}
|
|
||||||
value="warn"
|
|
||||||
label={_(msg`Warn`)}
|
|
||||||
onChange={onChange}
|
|
||||||
labelGroup={labelGroup}
|
|
||||||
/>
|
|
||||||
<SelectableBtn
|
|
||||||
current={current}
|
|
||||||
value="ignore"
|
|
||||||
label={_(msg`Show`)}
|
|
||||||
right
|
|
||||||
onChange={onChange}
|
|
||||||
labelGroup={labelGroup}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectableBtnProps {
|
|
||||||
current: string
|
|
||||||
value: LabelPreference
|
|
||||||
label: string
|
|
||||||
left?: boolean
|
|
||||||
right?: boolean
|
|
||||||
onChange: (v: LabelPreference) => void
|
|
||||||
labelGroup: ConfigurableLabelGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectableBtn({
|
|
||||||
current,
|
|
||||||
value,
|
|
||||||
label,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
onChange,
|
|
||||||
labelGroup,
|
|
||||||
}: SelectableBtnProps) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const palPrimary = usePalette('inverted')
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
style={[
|
|
||||||
styles.selectableBtn,
|
|
||||||
left && styles.selectableBtnLeft,
|
|
||||||
right && styles.selectableBtnRight,
|
|
||||||
pal.border,
|
|
||||||
current === value ? palPrimary.view : pal.view,
|
|
||||||
]}
|
|
||||||
onPress={() => onChange(value)}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={value}
|
|
||||||
accessibilityHint={_(
|
|
||||||
msg`Set ${value} for ${labelGroup} content moderation policy`,
|
|
||||||
)}>
|
|
||||||
<Text style={current === value ? palPrimary.text : pal.text}>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: 24,
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
paddingHorizontal: 2,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
scrollContainer: {
|
|
||||||
flex: 1,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
},
|
|
||||||
btnContainer: {
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
},
|
|
||||||
btnContainerMobile: {
|
|
||||||
paddingBottom: 40,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
agePrompt: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingLeft: 14,
|
|
||||||
paddingRight: 10,
|
|
||||||
paddingVertical: 8,
|
|
||||||
borderRadius: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
contentLabelPref: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingTop: 14,
|
|
||||||
paddingLeft: 4,
|
|
||||||
marginBottom: 14,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
selectableBtns: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginLeft: 10,
|
|
||||||
},
|
|
||||||
selectableBtn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderLeftWidth: 0,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
paddingVertical: 10,
|
|
||||||
},
|
|
||||||
selectableBtnLeft: {
|
|
||||||
borderTopLeftRadius: 8,
|
|
||||||
borderBottomLeftRadius: 8,
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
},
|
|
||||||
selectableBtnRight: {
|
|
||||||
borderTopRightRadius: 8,
|
|
||||||
borderBottomRightRadius: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
btn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%',
|
|
||||||
borderRadius: 32,
|
|
||||||
padding: 14,
|
|
||||||
backgroundColor: colors.gray1,
|
|
||||||
},
|
|
||||||
toggleBtn: {
|
|
||||||
paddingHorizontal: 0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -22,7 +22,6 @@ import * as ChangeHandleModal from './ChangeHandle'
|
|||||||
import * as WaitlistModal from './Waitlist'
|
import * as WaitlistModal from './Waitlist'
|
||||||
import * as InviteCodesModal from './InviteCodes'
|
import * as InviteCodesModal from './InviteCodes'
|
||||||
import * as AddAppPassword from './AddAppPasswords'
|
import * as AddAppPassword from './AddAppPasswords'
|
||||||
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
|
||||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||||
import * as BirthDateSettingsModal from './BirthDateSettings'
|
import * as BirthDateSettingsModal from './BirthDateSettings'
|
||||||
@@ -113,9 +112,6 @@ export function ModalsContainer() {
|
|||||||
} else if (activeModal?.name === 'add-app-password') {
|
} else if (activeModal?.name === 'add-app-password') {
|
||||||
snapPoints = AddAppPassword.snapPoints
|
snapPoints = AddAppPassword.snapPoints
|
||||||
element = <AddAppPassword.Component />
|
element = <AddAppPassword.Component />
|
||||||
} else if (activeModal?.name === 'content-filtering-settings') {
|
|
||||||
snapPoints = ContentFilteringSettingsModal.snapPoints
|
|
||||||
element = <ContentFilteringSettingsModal.Component />
|
|
||||||
} else if (activeModal?.name === 'content-languages-settings') {
|
} else if (activeModal?.name === 'content-languages-settings') {
|
||||||
snapPoints = ContentLanguagesSettingsModal.snapPoints
|
snapPoints = ContentLanguagesSettingsModal.snapPoints
|
||||||
element = <ContentLanguagesSettingsModal.Component />
|
element = <ContentLanguagesSettingsModal.Component />
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import * as ChangeHandleModal from './ChangeHandle'
|
|||||||
import * as WaitlistModal from './Waitlist'
|
import * as WaitlistModal from './Waitlist'
|
||||||
import * as InviteCodesModal from './InviteCodes'
|
import * as InviteCodesModal from './InviteCodes'
|
||||||
import * as AddAppPassword from './AddAppPasswords'
|
import * as AddAppPassword from './AddAppPasswords'
|
||||||
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
|
||||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||||
import * as BirthDateSettingsModal from './BirthDateSettings'
|
import * as BirthDateSettingsModal from './BirthDateSettings'
|
||||||
@@ -107,8 +106,6 @@ function Modal({modal}: {modal: ModalIface}) {
|
|||||||
element = <InviteCodesModal.Component />
|
element = <InviteCodesModal.Component />
|
||||||
} else if (modal.name === 'add-app-password') {
|
} else if (modal.name === 'add-app-password') {
|
||||||
element = <AddAppPassword.Component />
|
element = <AddAppPassword.Component />
|
||||||
} else if (modal.name === 'content-filtering-settings') {
|
|
||||||
element = <ContentFilteringSettingsModal.Component />
|
|
||||||
} else if (modal.name === 'content-languages-settings') {
|
} else if (modal.name === 'content-languages-settings') {
|
||||||
element = <ContentLanguagesSettingsModal.Component />
|
element = <ContentLanguagesSettingsModal.Component />
|
||||||
} else if (modal.name === 'post-languages-settings') {
|
} else if (modal.name === 'post-languages-settings') {
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {StyleSheet, View} from 'react-native'
|
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {TextLink} from '../util/Link'
|
|
||||||
import {ToggleButton} from '../util/forms/ToggleButton'
|
|
||||||
import {Button} from '../util/forms/Button'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {isIOS} from 'platform/detection'
|
|
||||||
import * as Toast from '../util/Toast'
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {
|
|
||||||
usePreferencesQuery,
|
|
||||||
usePreferencesSetAdultContentMutation,
|
|
||||||
} from '#/state/queries/preferences'
|
|
||||||
|
|
||||||
export function AdultContentEnabledPref() {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {data: preferences} = usePreferencesQuery()
|
|
||||||
const {mutate, variables} = usePreferencesSetAdultContentMutation()
|
|
||||||
const {openModal} = useModalControls()
|
|
||||||
|
|
||||||
const onSetAge = React.useCallback(
|
|
||||||
() => openModal({name: 'birth-date-settings'}),
|
|
||||||
[openModal],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onToggleAdultContent = React.useCallback(async () => {
|
|
||||||
if (isIOS) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
mutate({
|
|
||||||
enabled: !(
|
|
||||||
variables?.enabled ?? preferences?.moderationOpts.adultContentEnabled
|
|
||||||
),
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
Toast.show(
|
|
||||||
_(msg`There was an issue syncing your preferences with the server`),
|
|
||||||
)
|
|
||||||
logger.error('Failed to update preferences with server', {error: e})
|
|
||||||
}
|
|
||||||
}, [variables, preferences, mutate, _])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[pal.border, {borderTopWidth: 1, paddingHorizontal: 12}]}>
|
|
||||||
{isIOS ? (
|
|
||||||
preferences?.moderationOpts.adultContentEnabled ? null : (
|
|
||||||
<View style={{paddingVertical: 12}}>
|
|
||||||
<Text type="md" style={pal.textLight}>
|
|
||||||
<Trans>
|
|
||||||
Adult content can only be enabled via the Web at{' '}
|
|
||||||
<TextLink
|
|
||||||
style={pal.link}
|
|
||||||
href="https://bsky.app"
|
|
||||||
text="bsky.app"
|
|
||||||
/>
|
|
||||||
.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
) : (preferences?.userAge || 0) >= 18 ? (
|
|
||||||
<View style={{paddingVertical: 4}}>
|
|
||||||
<ToggleButton
|
|
||||||
type="default-light"
|
|
||||||
label={_(msg`Enable Adult Content`)}
|
|
||||||
isSelected={
|
|
||||||
variables?.enabled ??
|
|
||||||
preferences?.moderationOpts.adultContentEnabled ??
|
|
||||||
false
|
|
||||||
}
|
|
||||||
onPress={onToggleAdultContent}
|
|
||||||
style={styles.toggleBtn}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={styles.agePrompt}>
|
|
||||||
<Text type="md" style={[pal.text, {flex: 1}]}>
|
|
||||||
<Trans>You must be 18 or older to enable adult content.</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
agePrompt: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: 12,
|
|
||||||
},
|
|
||||||
toggleBtn: {
|
|
||||||
paddingHorizontal: 0,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {LabelPreference} from '@atproto/api'
|
|
||||||
import {StyleSheet, Pressable, View} from 'react-native'
|
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {
|
|
||||||
usePreferencesSetContentLabelMutation,
|
|
||||||
ConfigurableLabelGroup,
|
|
||||||
CONFIGURABLE_LABEL_GROUPS,
|
|
||||||
UsePreferencesQueryResponse,
|
|
||||||
} from '#/state/queries/preferences'
|
|
||||||
|
|
||||||
export function LabelGroupPref({
|
|
||||||
preferences,
|
|
||||||
labelGroup,
|
|
||||||
disabled,
|
|
||||||
}: {
|
|
||||||
preferences?: UsePreferencesQueryResponse
|
|
||||||
labelGroup: ConfigurableLabelGroup
|
|
||||||
disabled?: boolean
|
|
||||||
}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const visibility = preferences?.moderationOpts.labelGroups?.[labelGroup]
|
|
||||||
const {mutate, variables} = usePreferencesSetContentLabelMutation()
|
|
||||||
|
|
||||||
const onChange = React.useCallback(
|
|
||||||
(vis: LabelPreference) => {
|
|
||||||
mutate({labelGroup, visibility: vis})
|
|
||||||
},
|
|
||||||
[mutate, labelGroup],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[styles.labelGroupPref, pal.border]}>
|
|
||||||
<View style={s.flex1}>
|
|
||||||
<Text type="md-medium" style={[pal.text]}>
|
|
||||||
{CONFIGURABLE_LABEL_GROUPS[labelGroup].title}
|
|
||||||
</Text>
|
|
||||||
{typeof CONFIGURABLE_LABEL_GROUPS[labelGroup].subtitle === 'string' && (
|
|
||||||
<Text type="sm" style={[pal.textLight]}>
|
|
||||||
{CONFIGURABLE_LABEL_GROUPS[labelGroup].subtitle}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{disabled || !visibility ? (
|
|
||||||
<Text type="sm-bold" style={pal.textLight}>
|
|
||||||
<Trans context="action">Hide</Trans>
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<SelectGroup
|
|
||||||
current={variables?.visibility || visibility}
|
|
||||||
onChange={onChange}
|
|
||||||
labelGroup={labelGroup}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectGroupProps {
|
|
||||||
current: LabelPreference
|
|
||||||
onChange: (v: LabelPreference) => void
|
|
||||||
labelGroup: ConfigurableLabelGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectGroup({current, onChange, labelGroup}: SelectGroupProps) {
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.selectableBtns}>
|
|
||||||
<SelectableBtn
|
|
||||||
current={current}
|
|
||||||
value="hide"
|
|
||||||
label={_(msg`Hide`)}
|
|
||||||
left
|
|
||||||
onChange={onChange}
|
|
||||||
labelGroup={labelGroup}
|
|
||||||
/>
|
|
||||||
<SelectableBtn
|
|
||||||
current={current}
|
|
||||||
value="warn"
|
|
||||||
label={_(msg`Warn`)}
|
|
||||||
onChange={onChange}
|
|
||||||
labelGroup={labelGroup}
|
|
||||||
/>
|
|
||||||
<SelectableBtn
|
|
||||||
current={current}
|
|
||||||
value="ignore"
|
|
||||||
label={_(msg`Show`)}
|
|
||||||
right
|
|
||||||
onChange={onChange}
|
|
||||||
labelGroup={labelGroup}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectableBtnProps {
|
|
||||||
current: string
|
|
||||||
value: LabelPreference
|
|
||||||
label: string
|
|
||||||
left?: boolean
|
|
||||||
right?: boolean
|
|
||||||
onChange: (v: LabelPreference) => void
|
|
||||||
labelGroup: ConfigurableLabelGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectableBtn({
|
|
||||||
current,
|
|
||||||
value,
|
|
||||||
label,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
onChange,
|
|
||||||
labelGroup,
|
|
||||||
}: SelectableBtnProps) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const palPrimary = usePalette('inverted')
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
style={[
|
|
||||||
styles.selectableBtn,
|
|
||||||
left && styles.selectableBtnLeft,
|
|
||||||
right && styles.selectableBtnRight,
|
|
||||||
pal.border,
|
|
||||||
current === value ? palPrimary.view : pal.view,
|
|
||||||
]}
|
|
||||||
onPress={() => onChange(value)}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={value}
|
|
||||||
accessibilityHint={_(
|
|
||||||
msg`Set ${value} for ${labelGroup} content moderation policy`,
|
|
||||||
)}>
|
|
||||||
<Text style={current === value ? palPrimary.text : pal.text}>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
labelGroupPref: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: 14,
|
|
||||||
paddingLeft: 14,
|
|
||||||
paddingRight: 10,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
selectableBtns: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginLeft: 10,
|
|
||||||
},
|
|
||||||
selectableBtn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderLeftWidth: 0,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
paddingVertical: 10,
|
|
||||||
},
|
|
||||||
selectableBtnLeft: {
|
|
||||||
borderTopLeftRadius: 8,
|
|
||||||
borderBottomLeftRadius: 8,
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
},
|
|
||||||
selectableBtnRight: {
|
|
||||||
borderTopRightRadius: 8,
|
|
||||||
borderBottomRightRadius: 8,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {Pressable, StyleSheet, View} from 'react-native'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {useNavigation} from '@react-navigation/native'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {TextLink} from '../util/Link'
|
|
||||||
import {CenteredView} from '../util/Views'
|
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
|
||||||
import {makeProfileLink} from 'lib/routes/links'
|
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
|
||||||
import {BACK_HITSLOP} from 'lib/constants'
|
|
||||||
import {isNative} from 'platform/detection'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useSetDrawerOpen} from '#/state/shell'
|
|
||||||
import {emitSoftReset} from '#/state/events'
|
|
||||||
import {AppBskyModerationDefs} from '@atproto/api'
|
|
||||||
import {HandIcon} from '#/lib/icons'
|
|
||||||
import {shareUrl} from 'lib/sharing'
|
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
|
||||||
import {Button} from '../util/forms/Button'
|
|
||||||
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
|
|
||||||
import {useSession} from '#/state/session'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
|
|
||||||
export function ModServiceHeader({
|
|
||||||
info,
|
|
||||||
}: {
|
|
||||||
info: AppBskyModerationDefs.ModServiceViewDetailed
|
|
||||||
}) {
|
|
||||||
const setDrawerOpen = useSetDrawerOpen()
|
|
||||||
const navigation = useNavigation<NavigationProp>()
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const canGoBack = navigation.canGoBack()
|
|
||||||
|
|
||||||
const onPressBack = React.useCallback(() => {
|
|
||||||
if (navigation.canGoBack()) {
|
|
||||||
navigation.goBack()
|
|
||||||
} else {
|
|
||||||
navigation.navigate('Home')
|
|
||||||
}
|
|
||||||
}, [navigation])
|
|
||||||
|
|
||||||
const onPressMenu = React.useCallback(() => {
|
|
||||||
setDrawerOpen(true)
|
|
||||||
}, [setDrawerOpen])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CenteredView style={pal.view}>
|
|
||||||
{isMobile && (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
paddingTop: isNative ? 0 : 8,
|
|
||||||
paddingBottom: 8,
|
|
||||||
paddingHorizontal: isMobile ? 12 : 14,
|
|
||||||
},
|
|
||||||
pal.border,
|
|
||||||
]}>
|
|
||||||
<Pressable
|
|
||||||
testID="headerDrawerBtn"
|
|
||||||
onPress={canGoBack ? onPressBack : onPressMenu}
|
|
||||||
hitSlop={BACK_HITSLOP}
|
|
||||||
style={canGoBack ? styles.backBtn : styles.backBtnWide}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={canGoBack ? 'Back' : 'Menu'}
|
|
||||||
accessibilityHint="">
|
|
||||||
{canGoBack ? (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
size={18}
|
|
||||||
icon="angle-left"
|
|
||||||
style={[styles.backIcon, pal.text]}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
size={18}
|
|
||||||
icon="bars"
|
|
||||||
style={[styles.backIcon, pal.textLight]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
<View style={{flex: 1}} />
|
|
||||||
<CommonControls info={info} />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
gap: 10,
|
|
||||||
paddingTop: 14,
|
|
||||||
paddingBottom: 14,
|
|
||||||
paddingHorizontal: isMobile ? 12 : 14,
|
|
||||||
}}>
|
|
||||||
<View style={{alignSelf: 'center'}}>
|
|
||||||
<HandIcon style={pal.text} size={32} strokeWidth={5.5} />
|
|
||||||
</View>
|
|
||||||
<View style={{flex: 1}}>
|
|
||||||
<TextLink
|
|
||||||
testID="headerTitle"
|
|
||||||
type="title-xl"
|
|
||||||
href={makeProfileLink(info.creator, 'modservice')}
|
|
||||||
style={[pal.text, {fontWeight: 'bold'}]}
|
|
||||||
text={
|
|
||||||
info.creator.displayName
|
|
||||||
? sanitizeDisplayName(info.creator.displayName)
|
|
||||||
: sanitizeHandle(info.creator.handle, '@')
|
|
||||||
}
|
|
||||||
onPress={emitSoftReset}
|
|
||||||
numberOfLines={4}
|
|
||||||
/>
|
|
||||||
<Text type="xl" style={[pal.textLight]} numberOfLines={1}>
|
|
||||||
<Trans>Moderation service</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<Button type="primary" label={_(msg`Subscribe`)} />
|
|
||||||
{!isMobile && <CommonControls info={info} />}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</CenteredView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommonControls({
|
|
||||||
info,
|
|
||||||
}: {
|
|
||||||
info: AppBskyModerationDefs.ModServiceViewDetailed
|
|
||||||
}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {hasSession} = useSession()
|
|
||||||
const {openModal} = useModalControls()
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
const onPressShare = React.useCallback(() => {
|
|
||||||
const url = makeProfileLink(info.creator, 'modservice')
|
|
||||||
shareUrl(url)
|
|
||||||
// track('CustomFeed:Share') TODO
|
|
||||||
}, [info /*, track*/])
|
|
||||||
|
|
||||||
const onPressReport = React.useCallback(() => {
|
|
||||||
if (!info) return
|
|
||||||
openModal({
|
|
||||||
name: 'report',
|
|
||||||
uri: info.uri,
|
|
||||||
cid: info.cid,
|
|
||||||
})
|
|
||||||
}, [openModal, info])
|
|
||||||
|
|
||||||
const dropdownItems: DropdownItem[] = React.useMemo(() => {
|
|
||||||
return [
|
|
||||||
hasSession && {
|
|
||||||
testID: 'modHeaderDropdownReportBtn',
|
|
||||||
label: _(msg`Report mod service`),
|
|
||||||
onPress: onPressReport,
|
|
||||||
icon: {
|
|
||||||
ios: {
|
|
||||||
name: 'exclamationmark.triangle',
|
|
||||||
},
|
|
||||||
android: 'ic_menu_report_image',
|
|
||||||
web: 'circle-exclamation',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testID: 'modHeaderDropdownShareBtn',
|
|
||||||
label: _(msg`Share mod service`),
|
|
||||||
onPress: onPressShare,
|
|
||||||
icon: {
|
|
||||||
ios: {
|
|
||||||
name: 'square.and.arrow.up',
|
|
||||||
},
|
|
||||||
android: 'ic_menu_share',
|
|
||||||
web: 'share',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
].filter(Boolean) as DropdownItem[]
|
|
||||||
}, [hasSession, onPressReport, onPressShare, _])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NativeDropdown
|
|
||||||
testID="headerDropdownBtn"
|
|
||||||
items={dropdownItems}
|
|
||||||
accessibilityLabel={_(msg`More options`)}
|
|
||||||
accessibilityHint="">
|
|
||||||
<View style={[pal.viewLight, styles.btn, {marginLeft: 6}]}>
|
|
||||||
<FontAwesomeIcon icon="ellipsis" size={20} color={pal.colors.text} />
|
|
||||||
</View>
|
|
||||||
</NativeDropdown>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
backBtn: {
|
|
||||||
width: 20,
|
|
||||||
height: 30,
|
|
||||||
},
|
|
||||||
backBtnWide: {
|
|
||||||
width: 20,
|
|
||||||
height: 30,
|
|
||||||
paddingHorizontal: 6,
|
|
||||||
},
|
|
||||||
backIcon: {
|
|
||||||
marginTop: 6,
|
|
||||||
},
|
|
||||||
btn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 6,
|
|
||||||
paddingVertical: 7,
|
|
||||||
paddingHorizontal: 14,
|
|
||||||
borderRadius: 50,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {View} from 'react-native'
|
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
|
||||||
import {LabelGroupPref} from './LabelGroupPref'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
|
|
||||||
export function ModServicePrefs({}: {}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {data: preferences} = usePreferencesQuery()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View testID="modServicePrefs" style={[pal.border, {borderBottomWidth: 1}]}>
|
|
||||||
<View style={{paddingHorizontal: 14, paddingBottom: 8}}>
|
|
||||||
<Text type="2xl-bold">Settings</Text>
|
|
||||||
</View>
|
|
||||||
<LabelGroupPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="nsfw"
|
|
||||||
disabled={!preferences?.moderationOpts.adultContentEnabled}
|
|
||||||
/>
|
|
||||||
<LabelGroupPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="nudity"
|
|
||||||
disabled={!preferences?.moderationOpts.adultContentEnabled}
|
|
||||||
/>
|
|
||||||
<LabelGroupPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="suggestive"
|
|
||||||
disabled={!preferences?.moderationOpts.adultContentEnabled}
|
|
||||||
/>
|
|
||||||
<LabelGroupPref
|
|
||||||
preferences={preferences}
|
|
||||||
labelGroup="gore"
|
|
||||||
disabled={!preferences?.moderationOpts.adultContentEnabled}
|
|
||||||
/>
|
|
||||||
<LabelGroupPref preferences={preferences} labelGroup="hate" />
|
|
||||||
<LabelGroupPref preferences={preferences} labelGroup="spam" />
|
|
||||||
<LabelGroupPref preferences={preferences} labelGroup="impersonation" />
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -302,7 +302,6 @@ let FeedItemInner = ({
|
|||||||
moderation={moderation}
|
moderation={moderation}
|
||||||
richText={richText}
|
richText={richText}
|
||||||
postEmbed={post.embed}
|
postEmbed={post.embed}
|
||||||
postAuthor={post.author}
|
|
||||||
/>
|
/>
|
||||||
<PostCtrls
|
<PostCtrls
|
||||||
post={post}
|
post={post}
|
||||||
|
|||||||
@@ -47,14 +47,13 @@ const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys(
|
|||||||
LABELS,
|
LABELS,
|
||||||
) as (keyof typeof LABELS)[]
|
) as (keyof typeof LABELS)[]
|
||||||
|
|
||||||
const MOCK_MOD_OPTS = {
|
const MOCK_MOD_PREFS = {
|
||||||
userDid: '',
|
|
||||||
adultContentEnabled: true,
|
adultContentEnabled: true,
|
||||||
labelGroups: {},
|
labelGroups: {},
|
||||||
mods: [
|
mods: [
|
||||||
{
|
{
|
||||||
did: 'did:plc:fake-labeler',
|
did: 'did:plc:fake-labeler',
|
||||||
enabled: true,
|
labels: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -225,12 +224,13 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
|||||||
|
|
||||||
const modOpts = React.useMemo(() => {
|
const modOpts = React.useMemo(() => {
|
||||||
return {
|
return {
|
||||||
...MOCK_MOD_OPTS,
|
|
||||||
userDid: isLoggedOut ? '' : isTargetMe ? did : 'did:web:alice.test',
|
userDid: isLoggedOut ? '' : isTargetMe ? did : 'did:web:alice.test',
|
||||||
adultContentEnabled: !noAdult,
|
prefs: {
|
||||||
labelGroups: {
|
...MOCK_MOD_PREFS,
|
||||||
[LABELS[label[0] as keyof typeof LABELS].groupId]:
|
adultContentEnabled: !noAdult,
|
||||||
visibility[0] as LabelPreference,
|
labels: {
|
||||||
|
[label[0]]: visibility[0] as LabelPreference,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [label, visibility, noAdult, isLoggedOut, isTargetMe, did])
|
}, [label, visibility, noAdult, isLoggedOut, isTargetMe, did])
|
||||||
@@ -353,11 +353,8 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
|||||||
targetFixed = 'content'
|
targetFixed = 'content'
|
||||||
}
|
}
|
||||||
const disabled =
|
const disabled =
|
||||||
!LABELS[labelValue].targets.includes(
|
isSelfLabel &&
|
||||||
targetFixed as LabelTarget,
|
LABELS[labelValue].flags.includes('no-self')
|
||||||
) ||
|
|
||||||
(isSelfLabel &&
|
|
||||||
LABELS[labelValue].flags.includes('no-self'))
|
|
||||||
return (
|
return (
|
||||||
<Toggle.Item
|
<Toggle.Item
|
||||||
key={labelValue}
|
key={labelValue}
|
||||||
|
|||||||
Reference in New Issue
Block a user