diff --git a/src/components/ModerationLabelPref/PreferenceButton.tsx b/src/components/ModerationLabelPref/PreferenceButton.tsx
new file mode 100644
index 0000000000..199b61ed7c
--- /dev/null
+++ b/src/components/ModerationLabelPref/PreferenceButton.tsx
@@ -0,0 +1,133 @@
+import React from 'react'
+import {Pressable} from 'react-native'
+import {useLingui} from '@lingui/react'
+import {msg, Trans} from '@lingui/macro'
+import {InterprettedLabelValueDefinition, LabelPreference} from '@atproto/api'
+
+import {
+ useLabelBehaviorDescription,
+ useLabelLongBehaviorDescription,
+} from '#/lib/moderation/useLabelBehaviorDescription'
+
+import {useTheme, atoms as a} from '#/alf'
+import * as Dialog from '#/components/Dialog'
+import {Text} from '#/components/Typography'
+import {Button, ButtonText, ButtonIcon} from '#/components/Button'
+import {ArrowTriangleBottom_Stroke2_Corner1_Rounded as ArrowTriangleBottom} from '../icons/ArrowTriangle'
+import {Check_Stroke2_Corner0_Rounded as Check} from '../icons/Check'
+
+export function PreferenceButton({
+ name,
+ pref,
+ labelValueDefinition,
+ onSelectPref,
+}: {
+ name: string
+ pref: LabelPreference
+ labelValueDefinition: InterprettedLabelValueDefinition
+ onSelectPref: (pref: LabelPreference) => void
+}) {
+ const {_} = useLingui()
+ const t = useTheme()
+ const control = Dialog.useDialogControl()
+
+ const settingDesc = useLabelBehaviorDescription(labelValueDefinition, pref)
+ const hideLabel = useLabelLongBehaviorDescription(
+ labelValueDefinition,
+ 'hide',
+ )
+ const warnLabel = useLabelLongBehaviorDescription(
+ labelValueDefinition,
+ 'warn',
+ )
+ const ignoreLabel = useLabelLongBehaviorDescription(
+ labelValueDefinition,
+ 'ignore',
+ )
+ const canWarn = !(
+ labelValueDefinition.blurs === 'none' &&
+ labelValueDefinition.severity === 'none'
+ )
+
+ return (
+ <>
+ control.open()}
+ accessibilityLabel={settingDesc}
+ accessibilityHint=""
+ style={[
+ a.flex_row,
+ a.align_center,
+ a.justify_end,
+ a.gap_xs,
+ a.py_xs,
+ a.rounded_2xs,
+ ]}>
+
+ {settingDesc}
+
+
+
+
+
+
+
+
+
+ {name}
+
+
+ Choose how this label should be handled.
+
+
+
+
+ {canWarn && (
+
+ )}
+
+
+
+
+ >
+ )
+}
diff --git a/src/components/ModerationLabelPref/PreferenceButton.web.tsx b/src/components/ModerationLabelPref/PreferenceButton.web.tsx
new file mode 100644
index 0000000000..8275686241
--- /dev/null
+++ b/src/components/ModerationLabelPref/PreferenceButton.web.tsx
@@ -0,0 +1,102 @@
+import React from 'react'
+import {View} from 'react-native'
+import {useLingui} from '@lingui/react'
+import {msg} from '@lingui/macro'
+import {InterprettedLabelValueDefinition, LabelPreference} from '@atproto/api'
+
+import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription'
+import {
+ NativeDropdown,
+ DropdownItem,
+} from '#/view/com/util/forms/NativeDropdown'
+
+import {useTheme, atoms as a} from '#/alf'
+import {Text} from '#/components/Typography'
+import {ArrowTriangleBottom_Stroke2_Corner1_Rounded as ArrowTriangleBottom} from '../icons/ArrowTriangle'
+import {useInteractionState} from '#/components/hooks/useInteractionState'
+
+const CHECK_ICON: DropdownItem['icon'] = {
+ web: ['fas', 'check'],
+ ios: {name: 'trash'}, //doesnt matter
+ android: '',
+}
+
+export function PreferenceButton({
+ pref,
+ labelValueDefinition,
+ onSelectPref,
+}: {
+ pref: LabelPreference
+ labelValueDefinition: InterprettedLabelValueDefinition
+ onSelectPref: (pref: LabelPreference) => void
+}) {
+ const {_} = useLingui()
+ const t = useTheme()
+
+ const {
+ state: hovered,
+ onIn: onHoverIn,
+ onOut: onHoverOut,
+ } = useInteractionState()
+
+ const settingDesc = useLabelBehaviorDescription(labelValueDefinition, pref)
+ const hideLabel = useLabelBehaviorDescription(labelValueDefinition, 'hide')
+ const warnLabel = useLabelBehaviorDescription(labelValueDefinition, 'warn')
+ const ignoreLabel = useLabelBehaviorDescription(
+ labelValueDefinition,
+ 'ignore',
+ )
+ const canWarn = !(
+ labelValueDefinition.blurs === 'none' &&
+ labelValueDefinition.severity === 'none'
+ )
+
+ const dropdownItems: DropdownItem[] = []
+ dropdownItems.push({
+ icon: pref === 'hide' ? CHECK_ICON : undefined,
+ label: hideLabel,
+ onPress: () => onSelectPref('hide'),
+ })
+ if (canWarn) {
+ dropdownItems.push({
+ icon: pref === 'warn' ? CHECK_ICON : undefined,
+ label: warnLabel,
+ onPress: () => onSelectPref('warn'),
+ })
+ }
+ dropdownItems.push({
+ icon: pref === 'ignore' ? CHECK_ICON : undefined,
+ label: ignoreLabel,
+ onPress: () => onSelectPref('ignore'),
+ })
+
+ return (
+
+
+
+ {settingDesc}
+
+
+
+
+ )
+}
diff --git a/src/components/ModerationLabelPref/index.tsx b/src/components/ModerationLabelPref/index.tsx
index af8c97c76a..f80e6d8387 100644
--- a/src/components/ModerationLabelPref/index.tsx
+++ b/src/components/ModerationLabelPref/index.tsx
@@ -1,24 +1,32 @@
import React from 'react'
import {View} from 'react-native'
-import {InterprettedLabelValueDefinition} from '@atproto/api'
+import {InterprettedLabelValueDefinition, LabelPreference} from '@atproto/api'
import {useLabelStrings} from '#/lib/moderation/useLabelStrings'
-import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription'
+import {
+ usePreferencesQuery,
+ usePreferencesSetContentLabelMutation,
+} from '#/state/queries/preferences'
import {useTheme, atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
-import {Button, ButtonText, ButtonIcon} from '#/components/Button'
-import {ArrowTriangleBottom_Stroke2_Corner1_Rounded as ArrowTriangleBottom} from '../icons/ArrowTriangle'
+import {PreferenceButton} from './PreferenceButton'
export function ModerationLabelPref({
labelValueDefinition,
+ labelerDid,
disabled,
}: {
labelValueDefinition: InterprettedLabelValueDefinition
+ labelerDid: string | undefined
disabled?: boolean
}) {
const t = useTheme()
const allLabelStrings = useLabelStrings()
+ const {data: preferences} = usePreferencesQuery()
+ const {mutate, variables} = usePreferencesSetContentLabelMutation()
+
+ const {identifier} = labelValueDefinition
const labelStrings = labelValueDefinition.locales[0] // TODO look up locale
? labelValueDefinition.locales[0]
: labelValueDefinition.identifier in allLabelStrings
@@ -27,9 +35,16 @@ export function ModerationLabelPref({
name: labelValueDefinition.identifier,
description: `Labeled "${labelValueDefinition.identifier}"`,
}
- const settingDesc = useLabelBehaviorDescription(labelValueDefinition, 'hide')
- // TODO add onChange behavior when mod prefs are updated
+ const savedPref = labelerDid
+ ? preferences?.moderationPrefs.mods.find(m => m.did === labelerDid)?.labels[
+ identifier
+ ]
+ : preferences?.moderationPrefs.labels[identifier]
+ const pref = variables?.visibility ?? savedPref ?? 'warn'
+
+ const onSelectPref = (newPref: LabelPreference) =>
+ mutate({label: identifier, visibility: newPref, labelerDid})
return (
@@ -39,21 +54,16 @@ export function ModerationLabelPref({
{labelStrings.description}
- {!disabled && (
-
-
- {settingDesc}
-
-
-
- )}
+
+ {!disabled && (
+
+ )}
+
)
}
diff --git a/src/lib/moderation/useLabelBehaviorDescription.ts b/src/lib/moderation/useLabelBehaviorDescription.ts
index 43110d72f7..e4624f3624 100644
--- a/src/lib/moderation/useLabelBehaviorDescription.ts
+++ b/src/lib/moderation/useLabelBehaviorDescription.ts
@@ -12,22 +12,58 @@ export function useLabelBehaviorDescription(
}
if (labelValueDef.blurs === 'content') {
if (pref === 'hide') {
- return _(msg`Hide content`)
+ return _(msg`Hide`)
}
- return _(msg`Warn content`)
+ return _(msg`Warn`)
} else if (labelValueDef.blurs === 'media') {
if (pref === 'hide') {
- return _(msg`Hide images`)
+ return _(msg`Hide`)
}
- return _(msg`Warn images`)
+ return _(msg`Blur images`)
} else if (labelValueDef.severity === 'alert') {
if (pref === 'hide') {
- return _(msg`Filter from feeds`)
+ return _(msg`Hide`)
}
return _(msg`Show warning`)
} else if (labelValueDef.severity === 'inform') {
if (pref === 'hide') {
- return _(msg`Filter from feeds`)
+ return _(msg`Hide`)
+ }
+ return _(msg`Show badge`)
+ } else {
+ if (pref === 'hide') {
+ return _(msg`Hide`)
+ }
+ return _(msg`Disabled`)
+ }
+}
+
+export function useLabelLongBehaviorDescription(
+ labelValueDef: InterprettedLabelValueDefinition,
+ pref: LabelPreference,
+) {
+ const {_} = useLingui()
+ if (pref === 'ignore') {
+ return _(msg`Disabled`)
+ }
+ if (labelValueDef.blurs === 'content') {
+ if (pref === 'hide') {
+ return _(msg`Warn content and filter from feeds`)
+ }
+ return _(msg`Warn content`)
+ } else if (labelValueDef.blurs === 'media') {
+ if (pref === 'hide') {
+ return _(msg`Blur images and filter from feeds`)
+ }
+ return _(msg`Blur images`)
+ } else if (labelValueDef.severity === 'alert') {
+ if (pref === 'hide') {
+ return _(msg`Show warning and filter from feeds`)
+ }
+ return _(msg`Show warning`)
+ } else if (labelValueDef.severity === 'inform') {
+ if (pref === 'hide') {
+ return _(msg`Show badge and filter from feeds`)
}
return _(msg`Show badge`)
} else {
diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx
index dbaeae315c..5f4c2f8228 100644
--- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx
@@ -137,7 +137,7 @@ let ProfileHeaderLabeler = ({
isPlaceholderProfile={isPlaceholderProfile}>
{isMe ? (
)
-}
+})
export function ProfileLabelsSectionInner({
moderationOpts,
labelerInfo,
+ scrollElRef,
+ headerHeight,
}: {
moderationOpts: ModerationOpts
labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed
+ scrollElRef: ListRef
+ headerHeight: number
}) {
const {_} = useLingui()
const t = useTheme()
+ const contextScrollHandlers = useScrollHandlers()
+
+ const scrollHandler = useAnimatedScrollHandler({
+ onBeginDrag(e, ctx) {
+ contextScrollHandlers.onBeginDrag?.(e, ctx)
+ },
+ onEndDrag(e, ctx) {
+ contextScrollHandlers.onEndDrag?.(e, ctx)
+ },
+ onScroll(e, ctx) {
+ contextScrollHandlers.onScroll?.(e, ctx)
+ },
+ })
+
const {labelValues} = labelerInfo.policies
const isSubscribed = moderationOpts.prefs.mods.find(
mod => mod.did === labelerInfo.creator.did,
@@ -93,66 +141,81 @@ export function ProfileLabelsSectionInner({
) as InterprettedLabelValueDefinition[]
}, [labelerInfo, labelValues])
+ console.log(headerHeight, scrollElRef.current)
+
return (
-
-
-
- Labels are annotations on users and content. They can be used to
- hide, warn, and categorize the network.
-
-
- {labelValues.length === 0 ? (
-
+ }}
+ contentOffset={{x: 0, y: headerHeight * -1}}
+ onScroll={scrollHandler}>
+
+
+
- This labeler hasn't declared what labels it publishes, and may not
- be active.
+ Labels are annotations on users and content. They can be used to
+ hide, warn, and categorize the network.
- ) : !isSubscribed ? (
-
-
- Subscribe to @{labelerInfo.creator.handle} to use these labels:
-
-
- ) : null}
-
- {labelDefs.length > 0 && (
-
- {labelDefs.map((labelDef, i) => {
- return (
-
- {i !== 0 && }
-
-
-
-
- )
- })}
+ {labelValues.length === 0 ? (
+
+
+ This labeler hasn't declared what labels it publishes, and may
+ not be active.
+
+
+ ) : !isSubscribed ? (
+
+
+ Subscribe to @{labelerInfo.creator.handle} to use these labels:
+
+
+ ) : null}
- )}
+ {labelDefs.length > 0 && (
+
+ {labelDefs.map((labelDef, i) => {
+ return (
+
+ {i !== 0 && }
+
+
+
+
+ )
+ })}
+
+ )}
-
+
+
)
}
diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts
index 76fff47ab7..2e5f35e16d 100644
--- a/src/state/queries/preferences/index.ts
+++ b/src/state/queries/preferences/index.ts
@@ -115,10 +115,10 @@ export function usePreferencesSetContentLabelMutation() {
return useMutation<
void,
unknown,
- {labelGroup: ConfigurableLabelGroup; visibility: LabelPreference}
+ {label: string; visibility: LabelPreference; labelerDid: string | undefined}
>({
- mutationFn: async ({labelGroup, visibility}) => {
- await getAgent().setContentLabelPref(labelGroup, visibility)
+ mutationFn: async ({label, visibility, labelerDid}) => {
+ await getAgent().setContentLabelPref(label, visibility, labelerDid)
// triggers a refetch
await queryClient.invalidateQueries({
queryKey: preferencesQueryKey,
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 2b0f01d733..61675e3f33 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -156,7 +156,7 @@ function ProfileScreenLoaded({
const likesSectionRef = React.useRef(null)
const feedsSectionRef = React.useRef(null)
const listsSectionRef = React.useRef(null)
- const filtersSectionRef = React.useRef(null)
+ const labelsSectionRef = React.useRef(null)
useSetTitle(combinedDisplayName(profile))
@@ -237,7 +237,7 @@ function ProfileScreenLoaded({
const scrollSectionToTop = React.useCallback(
(index: number) => {
if (index === filtersIndex) {
- filtersSectionRef.current?.scrollToTop()
+ labelsSectionRef.current?.scrollToTop()
} else if (index === postsIndex) {
postsSectionRef.current?.scrollToTop()
} else if (index === repliesIndex) {
@@ -347,16 +347,15 @@ function ProfileScreenLoaded({
onCurrentPageSelected={onCurrentPageSelected}
renderHeader={renderHeader}>
{showFiltersTab
- ? ({headerHeight, isFocused, scrollElRef}) => (
+ ? ({headerHeight, scrollElRef}) => (
)
: null}