Mods UI fixes (#3296)
* Fix report dialog buttons on Android by adjusting styles * Dry up label pref compzio/stable
parent
4ff2bb7aba
commit
5f39ca3187
|
@ -38,7 +38,7 @@ export function SelectLabelerView({
|
|||
|
||||
<Divider />
|
||||
|
||||
<View style={[a.gap_xs, {marginHorizontal: a.p_md.padding * -1}]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
{props.labelers.map(labeler => {
|
||||
return (
|
||||
<Button
|
||||
|
@ -63,17 +63,14 @@ function LabelerButton({
|
|||
const {hovered, pressed} = useButtonContext()
|
||||
const interacted = hovered || pressed
|
||||
|
||||
const styles = React.useMemo(() => {
|
||||
return {
|
||||
interacted: {
|
||||
backgroundColor: t.palette.contrast_50,
|
||||
},
|
||||
}
|
||||
}, [t])
|
||||
|
||||
return (
|
||||
<LabelingServiceCard.Outer
|
||||
style={[a.p_md, a.rounded_sm, interacted && styles.interacted]}>
|
||||
style={[
|
||||
a.p_md,
|
||||
a.rounded_sm,
|
||||
t.atoms.bg_contrast_25,
|
||||
interacted && t.atoms.bg_contrast_50,
|
||||
]}>
|
||||
<LabelingServiceCard.Avatar avatar={labeler.creator.avatar} />
|
||||
<LabelingServiceCard.Content>
|
||||
<LabelingServiceCard.Title
|
||||
|
|
|
@ -86,7 +86,7 @@ export function SelectReportOptionView({
|
|||
|
||||
<Divider />
|
||||
|
||||
<View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
{reportOptions.map(reportOption => {
|
||||
return (
|
||||
<Button
|
||||
|
@ -102,39 +102,37 @@ export function SelectReportOptionView({
|
|||
})}
|
||||
|
||||
{(props.params.type === 'post' || props.params.type === 'account') && (
|
||||
<View style={[a.pt_md, a.px_md]}>
|
||||
<View
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_lg,
|
||||
a.p_md,
|
||||
a.pl_lg,
|
||||
a.rounded_md,
|
||||
t.atoms.bg_contrast_900,
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_lg,
|
||||
a.p_md,
|
||||
a.pl_lg,
|
||||
a.rounded_md,
|
||||
t.atoms.bg_contrast_900,
|
||||
a.flex_1,
|
||||
t.atoms.text_inverted,
|
||||
a.italic,
|
||||
a.leading_snug,
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
a.flex_1,
|
||||
t.atoms.text_inverted,
|
||||
a.italic,
|
||||
a.leading_snug,
|
||||
]}>
|
||||
<Trans>Need to report a copyright violation?</Trans>
|
||||
</Text>
|
||||
<Link
|
||||
to={DMCA_LINK}
|
||||
label={_(msg`View details for reporting a copyright violation`)}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="secondary">
|
||||
<ButtonText>
|
||||
<Trans>View details</Trans>
|
||||
</ButtonText>
|
||||
<ButtonIcon position="right" icon={SquareArrowTopRight} />
|
||||
</Link>
|
||||
</View>
|
||||
<Trans>Need to report a copyright violation?</Trans>
|
||||
</Text>
|
||||
<Link
|
||||
to={DMCA_LINK}
|
||||
label={_(msg`View details for reporting a copyright violation`)}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="secondary">
|
||||
<ButtonText>
|
||||
<Trans>View details</Trans>
|
||||
</ButtonText>
|
||||
<ButtonIcon position="right" icon={SquareArrowTopRight} />
|
||||
</Link>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
@ -153,14 +151,6 @@ function ReportOptionButton({
|
|||
const {hovered, pressed} = useButtonContext()
|
||||
const interacted = hovered || pressed
|
||||
|
||||
const styles = React.useMemo(() => {
|
||||
return {
|
||||
interacted: {
|
||||
backgroundColor: t.palette.contrast_50,
|
||||
},
|
||||
}
|
||||
}, [t])
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
|
@ -171,7 +161,8 @@ function ReportOptionButton({
|
|||
a.p_md,
|
||||
a.rounded_md,
|
||||
{paddingRight: 70},
|
||||
interacted && styles.interacted,
|
||||
t.atoms.bg_contrast_25,
|
||||
interacted && t.atoms.bg_contrast_50,
|
||||
]}>
|
||||
<View style={[a.flex_1, a.gap_xs]}>
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
|
||||
|
@ -188,12 +179,7 @@ function ReportOptionButton({
|
|||
a.pr_md,
|
||||
{left: 'auto'},
|
||||
]}>
|
||||
<ChevronRight
|
||||
size="md"
|
||||
fill={
|
||||
hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color
|
||||
}
|
||||
/>
|
||||
<ChevronRight size="md" fill={t.atoms.text_contrast_low.color} />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {ReportOption} from '#/lib/moderation/useReportOptions'
|
||||
import {useMyLabelersQuery} from '#/state/queries/preferences'
|
||||
|
@ -31,6 +32,7 @@ export function ReportDialog(props: ReportDialogProps) {
|
|||
}
|
||||
|
||||
function ReportDialogInner(props: ReportDialogProps) {
|
||||
const {_} = useLingui()
|
||||
const {
|
||||
isLoading: isLabelerLoading,
|
||||
data: labelers,
|
||||
|
@ -44,7 +46,7 @@ function ReportDialogInner(props: ReportDialogProps) {
|
|||
})
|
||||
|
||||
return (
|
||||
<Dialog.ScrollableInner label="Report Dialog" ref={ref}>
|
||||
<Dialog.ScrollableInner label={_(msg`Report dialog`)} ref={ref}>
|
||||
{isLoading ? (
|
||||
<View style={[a.align_center, {height: 100}]}>
|
||||
<Loader size="xl" />
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
View,
|
||||
AccessibilityProps,
|
||||
StyleSheet,
|
||||
TextInput,
|
||||
TextInputProps,
|
||||
TextStyle,
|
||||
View,
|
||||
ViewStyle,
|
||||
StyleSheet,
|
||||
AccessibilityProps,
|
||||
} from 'react-native'
|
||||
|
||||
import {mergeRefs} from '#/lib/merge-refs'
|
||||
import {HITSLOP_20} from 'lib/constants'
|
||||
import {useTheme, atoms as a, web, android} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {android, atoms as a, useTheme, web} from '#/alf'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {mergeRefs} from '#/lib/merge-refs'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
const Context = React.createContext<{
|
||||
inputRef: React.RefObject<TextInput> | null
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg} from '@lingui/macro'
|
||||
|
||||
import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
|
||||
import {
|
||||
usePreferencesQuery,
|
||||
usePreferencesSetContentLabelMutation,
|
||||
} from '#/state/queries/preferences'
|
||||
|
||||
import {useTheme, atoms as a} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||
|
||||
export function GlobalModerationLabelPref({
|
||||
labelValueDefinition,
|
||||
disabled,
|
||||
}: {
|
||||
labelValueDefinition: InterpretedLabelValueDefinition
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
|
||||
const {identifier} = labelValueDefinition
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {mutate, variables} = usePreferencesSetContentLabelMutation()
|
||||
const savedPref = preferences?.moderationPrefs.labels[identifier]
|
||||
const pref = variables?.visibility ?? savedPref ?? 'warn'
|
||||
|
||||
const allLabelStrings = useGlobalLabelStrings()
|
||||
const labelStrings =
|
||||
labelValueDefinition.identifier in allLabelStrings
|
||||
? allLabelStrings[labelValueDefinition.identifier]
|
||||
: {
|
||||
name: labelValueDefinition.identifier,
|
||||
description: `Labeled "${labelValueDefinition.identifier}"`,
|
||||
}
|
||||
|
||||
const labelOptions = {
|
||||
hide: _(msg`Hide`),
|
||||
warn: _(msg`Warn`),
|
||||
ignore: _(msg`Show`),
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.justify_between,
|
||||
a.gap_sm,
|
||||
a.py_md,
|
||||
a.pl_lg,
|
||||
a.pr_md,
|
||||
a.align_center,
|
||||
]}>
|
||||
<View style={[a.gap_xs, a.flex_1]}>
|
||||
<Text style={[a.font_bold]}>{labelStrings.name}</Text>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
|
||||
{labelStrings.description}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[a.justify_center, {minHeight: 35}]}>
|
||||
{!disabled && (
|
||||
<ToggleButton.Group
|
||||
label={_(
|
||||
msg`Configure content filtering setting for category: ${labelStrings.name.toLowerCase()}`,
|
||||
)}
|
||||
values={[pref]}
|
||||
onChange={newPref =>
|
||||
mutate({
|
||||
label: identifier,
|
||||
visibility: newPref[0] as LabelPreference,
|
||||
labelerDid: undefined,
|
||||
})
|
||||
}>
|
||||
<ToggleButton.Button name="ignore" label={labelOptions.ignore}>
|
||||
{labelOptions.ignore}
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="warn" label={labelOptions.warn}>
|
||||
{labelOptions.warn}
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="hide" label={labelOptions.hide}>
|
||||
{labelOptions.hide}
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
|
||||
import {
|
||||
usePreferencesQuery,
|
||||
usePreferencesSetContentLabelMutation,
|
||||
} from '#/state/queries/preferences'
|
||||
import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription'
|
||||
import {getLabelStrings} from '#/lib/moderation/useLabelInfo'
|
||||
|
||||
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {InlineLink} from '#/components/Link'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '../icons/CircleInfo'
|
||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||
|
||||
export function Outer({children}: React.PropsWithChildren<{}>) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_lg,
|
||||
a.justify_between,
|
||||
a.flex_wrap,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function Content({
|
||||
children,
|
||||
name,
|
||||
description,
|
||||
}: React.PropsWithChildren<{
|
||||
name: string
|
||||
description: string
|
||||
}>) {
|
||||
const t = useTheme()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
|
||||
return (
|
||||
<View style={[a.gap_xs, a.flex_1]}>
|
||||
<Text style={[a.font_bold, gtPhone ? a.text_sm : a.text_md]}>{name}</Text>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
|
||||
{description}
|
||||
</Text>
|
||||
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function Buttons({
|
||||
name,
|
||||
values,
|
||||
onChange,
|
||||
ignoreLabel,
|
||||
warnLabel,
|
||||
hideLabel,
|
||||
}: {
|
||||
name: string
|
||||
values: ToggleButton.GroupProps['values']
|
||||
onChange: ToggleButton.GroupProps['onChange']
|
||||
ignoreLabel?: string
|
||||
warnLabel?: string
|
||||
hideLabel?: string
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
|
||||
return (
|
||||
<View style={[{minHeight: 35}, gtPhone ? undefined : a.w_full]}>
|
||||
<ToggleButton.Group
|
||||
label={_(
|
||||
msg`Configure content filtering setting for category: ${name}`,
|
||||
)}
|
||||
values={values}
|
||||
onChange={onChange}>
|
||||
{ignoreLabel && (
|
||||
<ToggleButton.Button name="ignore" label={ignoreLabel}>
|
||||
{ignoreLabel}
|
||||
</ToggleButton.Button>
|
||||
)}
|
||||
{warnLabel && (
|
||||
<ToggleButton.Button name="warn" label={warnLabel}>
|
||||
{warnLabel}
|
||||
</ToggleButton.Button>
|
||||
)}
|
||||
{hideLabel && (
|
||||
<ToggleButton.Button name="hide" label={hideLabel}>
|
||||
{hideLabel}
|
||||
</ToggleButton.Button>
|
||||
)}
|
||||
</ToggleButton.Group>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* For use on the global Moderation screen to set prefs for a "global" label,
|
||||
* not scoped to a single labeler.
|
||||
*/
|
||||
export function GlobalLabelPreference({
|
||||
labelDefinition,
|
||||
disabled,
|
||||
}: {
|
||||
labelDefinition: InterpretedLabelValueDefinition
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
|
||||
const {identifier} = labelDefinition
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {mutate, variables} = usePreferencesSetContentLabelMutation()
|
||||
const savedPref = preferences?.moderationPrefs.labels[identifier]
|
||||
const pref = variables?.visibility ?? savedPref ?? 'warn'
|
||||
|
||||
const allLabelStrings = useGlobalLabelStrings()
|
||||
const labelStrings =
|
||||
labelDefinition.identifier in allLabelStrings
|
||||
? allLabelStrings[labelDefinition.identifier]
|
||||
: {
|
||||
name: labelDefinition.identifier,
|
||||
description: `Labeled "${labelDefinition.identifier}"`,
|
||||
}
|
||||
|
||||
const labelOptions = {
|
||||
hide: _(msg`Hide`),
|
||||
warn: _(msg`Warn`),
|
||||
ignore: _(msg`Show`),
|
||||
}
|
||||
|
||||
return (
|
||||
<Outer>
|
||||
<Content
|
||||
name={labelStrings.name}
|
||||
description={labelStrings.description}
|
||||
/>
|
||||
{!disabled && (
|
||||
<Buttons
|
||||
name={labelStrings.name.toLowerCase()}
|
||||
values={[pref]}
|
||||
onChange={values => {
|
||||
mutate({
|
||||
label: identifier,
|
||||
visibility: values[0] as LabelPreference,
|
||||
labelerDid: undefined,
|
||||
})
|
||||
}}
|
||||
ignoreLabel={labelOptions.ignore}
|
||||
warnLabel={labelOptions.warn}
|
||||
hideLabel={labelOptions.hide}
|
||||
/>
|
||||
)}
|
||||
</Outer>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* For use on individual labeler pages
|
||||
*/
|
||||
export function LabelerLabelPreference({
|
||||
labelDefinition,
|
||||
disabled,
|
||||
labelerDid,
|
||||
}: {
|
||||
labelDefinition: InterpretedLabelValueDefinition
|
||||
disabled?: boolean
|
||||
labelerDid?: string
|
||||
}) {
|
||||
const {i18n} = useLingui()
|
||||
const t = useTheme()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
|
||||
const isGlobalLabel = !labelDefinition.definedBy
|
||||
const {identifier} = labelDefinition
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {mutate, variables} = usePreferencesSetContentLabelMutation()
|
||||
const savedPref =
|
||||
labelerDid && !isGlobalLabel
|
||||
? preferences?.moderationPrefs.labelers.find(l => l.did === labelerDid)
|
||||
?.labels[identifier]
|
||||
: preferences?.moderationPrefs.labels[identifier]
|
||||
const pref =
|
||||
variables?.visibility ??
|
||||
savedPref ??
|
||||
labelDefinition.defaultSetting ??
|
||||
'warn'
|
||||
|
||||
// does the 'warn' setting make sense for this label?
|
||||
const canWarn = !(
|
||||
labelDefinition.blurs === 'none' && labelDefinition.severity === 'none'
|
||||
)
|
||||
// is this label adult only?
|
||||
const adultOnly = labelDefinition.flags.includes('adult')
|
||||
// is this label disabled because it's adult only?
|
||||
const adultDisabled =
|
||||
adultOnly && !preferences?.moderationPrefs.adultContentEnabled
|
||||
// are there any reasons we cant configure this label here?
|
||||
const cantConfigure = isGlobalLabel || adultDisabled
|
||||
const showConfig = !disabled && (gtPhone || !cantConfigure)
|
||||
|
||||
// adjust the pref based on whether warn is available
|
||||
let prefAdjusted = pref
|
||||
if (adultDisabled) {
|
||||
prefAdjusted = 'hide'
|
||||
} else if (!canWarn && pref === 'warn') {
|
||||
prefAdjusted = 'ignore'
|
||||
}
|
||||
|
||||
// grab localized descriptions of the label and its settings
|
||||
const currentPrefLabel = useLabelBehaviorDescription(
|
||||
labelDefinition,
|
||||
prefAdjusted,
|
||||
)
|
||||
const hideLabel = useLabelBehaviorDescription(labelDefinition, 'hide')
|
||||
const warnLabel = useLabelBehaviorDescription(labelDefinition, 'warn')
|
||||
const ignoreLabel = useLabelBehaviorDescription(labelDefinition, 'ignore')
|
||||
const globalLabelStrings = useGlobalLabelStrings()
|
||||
const labelStrings = getLabelStrings(
|
||||
i18n.locale,
|
||||
globalLabelStrings,
|
||||
labelDefinition,
|
||||
)
|
||||
|
||||
return (
|
||||
<Outer>
|
||||
<Content name={labelStrings.name} description={labelStrings.description}>
|
||||
{cantConfigure && (
|
||||
<View style={[a.flex_row, a.gap_xs, a.align_center, a.mt_xs]}>
|
||||
<CircleInfo size="sm" fill={t.atoms.text_contrast_high.color} />
|
||||
|
||||
<Text
|
||||
style={[t.atoms.text_contrast_medium, a.font_semibold, a.italic]}>
|
||||
{adultDisabled ? (
|
||||
<Trans>Adult content is disabled.</Trans>
|
||||
) : isGlobalLabel ? (
|
||||
<Trans>
|
||||
Configured in{' '}
|
||||
<InlineLink to="/moderation" style={a.text_sm}>
|
||||
moderation settings
|
||||
</InlineLink>
|
||||
.
|
||||
</Trans>
|
||||
) : null}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</Content>
|
||||
|
||||
{showConfig && (
|
||||
<View style={[gtPhone ? undefined : a.w_full]}>
|
||||
{cantConfigure ? (
|
||||
<View
|
||||
style={[
|
||||
{minHeight: 35},
|
||||
a.px_md,
|
||||
a.py_md,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_low]}>
|
||||
{currentPrefLabel}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Buttons
|
||||
name={labelStrings.name.toLowerCase()}
|
||||
values={[pref]}
|
||||
onChange={values => {
|
||||
mutate({
|
||||
label: identifier,
|
||||
visibility: values[0] as LabelPreference,
|
||||
labelerDid,
|
||||
})
|
||||
}}
|
||||
ignoreLabel={ignoreLabel}
|
||||
warnLabel={canWarn ? warnLabel : undefined}
|
||||
hideLabel={hideLabel}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</Outer>
|
||||
)
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
|
||||
import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription'
|
||||
import {
|
||||
usePreferencesQuery,
|
||||
usePreferencesSetContentLabelMutation,
|
||||
} from '#/state/queries/preferences'
|
||||
import {getLabelStrings} from '#/lib/moderation/useLabelInfo'
|
||||
|
||||
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {InlineLink} from '#/components/Link'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '../icons/CircleInfo'
|
||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||
|
||||
export function ModerationLabelPref({
|
||||
labelValueDefinition,
|
||||
labelerDid,
|
||||
disabled,
|
||||
}: {
|
||||
labelValueDefinition: InterpretedLabelValueDefinition
|
||||
labelerDid: string | undefined
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const {_, i18n} = useLingui()
|
||||
const t = useTheme()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
|
||||
const isGlobalLabel = !labelValueDefinition.definedBy
|
||||
const {identifier} = labelValueDefinition
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {mutate, variables} = usePreferencesSetContentLabelMutation()
|
||||
const savedPref =
|
||||
labelerDid && !isGlobalLabel
|
||||
? preferences?.moderationPrefs.labelers.find(l => l.did === labelerDid)
|
||||
?.labels[identifier]
|
||||
: preferences?.moderationPrefs.labels[identifier]
|
||||
const pref =
|
||||
variables?.visibility ??
|
||||
savedPref ??
|
||||
labelValueDefinition.defaultSetting ??
|
||||
'warn'
|
||||
|
||||
// does the 'warn' setting make sense for this label?
|
||||
const canWarn = !(
|
||||
labelValueDefinition.blurs === 'none' &&
|
||||
labelValueDefinition.severity === 'none'
|
||||
)
|
||||
// is this label adult only?
|
||||
const adultOnly = labelValueDefinition.flags.includes('adult')
|
||||
// is this label disabled because it's adult only?
|
||||
const adultDisabled =
|
||||
adultOnly && !preferences?.moderationPrefs.adultContentEnabled
|
||||
// are there any reasons we cant configure this label here?
|
||||
const cantConfigure = isGlobalLabel || adultDisabled
|
||||
const showConfig = !disabled && (gtPhone || !cantConfigure)
|
||||
|
||||
// adjust the pref based on whether warn is available
|
||||
let prefAdjusted = pref
|
||||
if (adultDisabled) {
|
||||
prefAdjusted = 'hide'
|
||||
} else if (!canWarn && pref === 'warn') {
|
||||
prefAdjusted = 'ignore'
|
||||
}
|
||||
|
||||
// grab localized descriptions of the label and its settings
|
||||
const currentPrefLabel = useLabelBehaviorDescription(
|
||||
labelValueDefinition,
|
||||
prefAdjusted,
|
||||
)
|
||||
const hideLabel = useLabelBehaviorDescription(labelValueDefinition, 'hide')
|
||||
const warnLabel = useLabelBehaviorDescription(labelValueDefinition, 'warn')
|
||||
const ignoreLabel = useLabelBehaviorDescription(
|
||||
labelValueDefinition,
|
||||
'ignore',
|
||||
)
|
||||
const globalLabelStrings = useGlobalLabelStrings()
|
||||
const labelStrings = getLabelStrings(
|
||||
i18n.locale,
|
||||
globalLabelStrings,
|
||||
labelValueDefinition,
|
||||
)
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_lg,
|
||||
a.justify_between,
|
||||
a.flex_wrap,
|
||||
]}>
|
||||
<View style={[a.gap_xs, a.flex_1]}>
|
||||
<Text style={[a.font_bold, gtPhone ? a.text_sm : a.text_md]}>
|
||||
{labelStrings.name}
|
||||
</Text>
|
||||
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
|
||||
{labelStrings.description}
|
||||
</Text>
|
||||
|
||||
{cantConfigure && (
|
||||
<View style={[a.flex_row, a.gap_xs, a.align_center, a.mt_xs]}>
|
||||
<CircleInfo size="sm" fill={t.atoms.text_contrast_high.color} />
|
||||
|
||||
<Text
|
||||
style={[t.atoms.text_contrast_medium, a.font_semibold, a.italic]}>
|
||||
{adultDisabled ? (
|
||||
<Trans>Adult content is disabled.</Trans>
|
||||
) : isGlobalLabel ? (
|
||||
<Trans>
|
||||
Configured in{' '}
|
||||
<InlineLink to="/moderation" style={a.text_sm}>
|
||||
moderation settings
|
||||
</InlineLink>
|
||||
.
|
||||
</Trans>
|
||||
) : null}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{showConfig && (
|
||||
<View style={[gtPhone ? undefined : a.w_full]}>
|
||||
{cantConfigure ? (
|
||||
<View
|
||||
style={[
|
||||
{minHeight: 35},
|
||||
a.px_md,
|
||||
a.py_md,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<Text style={[a.font_bold, t.atoms.text_contrast_low]}>
|
||||
{currentPrefLabel}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[{minHeight: 35}]}>
|
||||
<ToggleButton.Group
|
||||
label={_(
|
||||
msg`Configure content filtering setting for category: ${labelStrings.name.toLowerCase()}`,
|
||||
)}
|
||||
values={[prefAdjusted]}
|
||||
onChange={newPref =>
|
||||
mutate({
|
||||
label: identifier,
|
||||
visibility: newPref[0] as LabelPreference,
|
||||
labelerDid,
|
||||
})
|
||||
}>
|
||||
<ToggleButton.Button name="ignore" label={ignoreLabel}>
|
||||
{ignoreLabel}
|
||||
</ToggleButton.Button>
|
||||
{canWarn && (
|
||||
<ToggleButton.Button name="warn" label={warnLabel}>
|
||||
{warnLabel}
|
||||
</ToggleButton.Button>
|
||||
)}
|
||||
<ToggleButton.Button name="hide" label={hideLabel}>
|
||||
{hideLabel}
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
|
@ -41,7 +41,7 @@ import {InlineLink, Link} from '#/components/Link'
|
|||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as LabelingService from '#/components/LabelingServiceCard'
|
||||
import {GlobalModerationLabelPref} from '#/components/moderation/GlobalModerationLabelPref'
|
||||
import {GlobalLabelPreference} from '#/components/moderation/LabelPreference'
|
||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
||||
|
@ -352,17 +352,17 @@ export function ModerationScreenInner({
|
|||
)}
|
||||
{!isUnderage && adultContentEnabled && (
|
||||
<>
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.porn} />
|
||||
<GlobalLabelPreference labelDefinition={LABELS.porn} />
|
||||
<Divider />
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.sexual} />
|
||||
<GlobalLabelPreference labelDefinition={LABELS.sexual} />
|
||||
<Divider />
|
||||
<GlobalModerationLabelPref
|
||||
labelValueDefinition={LABELS['graphic-media']}
|
||||
<GlobalLabelPreference
|
||||
labelDefinition={LABELS['graphic-media']}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.nudity} />
|
||||
<GlobalLabelPreference labelDefinition={LABELS.nudity} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import {Loader} from '#/components/Loader'
|
|||
import {Divider} from '#/components/Divider'
|
||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||
import {ErrorState} from '../ErrorState'
|
||||
import {ModerationLabelPref} from '#/components/moderation/ModerationLabelPref'
|
||||
import {LabelerLabelPreference} from '#/components/moderation/LabelPreference'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||
|
||||
interface LabelsSectionProps {
|
||||
|
@ -197,9 +197,9 @@ export function ProfileLabelsSectionInner({
|
|||
return (
|
||||
<React.Fragment key={labelDef.identifier}>
|
||||
{i !== 0 && <Divider />}
|
||||
<ModerationLabelPref
|
||||
<LabelerLabelPreference
|
||||
disabled={isSubscribed ? undefined : true}
|
||||
labelValueDefinition={labelDef}
|
||||
labelDefinition={labelDef}
|
||||
labelerDid={labelerInfo.creator.did}
|
||||
/>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
|
||||
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
|
||||
import {CenteredView} from '../util/Views'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
import {Logotype} from '#/view/icons/Logotype'
|
||||
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
|
||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {CenteredView} from '../util/Views'
|
||||
|
||||
export const SplashScreen = ({
|
||||
onPressSignin,
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import React from 'react'
|
||||
import {View, Pressable} from 'react-native'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
|
||||
import {CenteredView} from '../util/Views'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
import {Logotype} from '#/view/icons/Logotype'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {InlineLink} from '#/components/Link'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {CenteredView} from '../util/Views'
|
||||
|
||||
export const SplashScreen = ({
|
||||
onDismiss,
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
import React, {useEffect, useRef} from 'react'
|
||||
import {StyleSheet, useWindowDimensions, View} from 'react-native'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {List, ListMethods} from '../util/List'
|
||||
import {PostThreadItem} from './PostThreadItem'
|
||||
import {ComposePrompt} from '../composer/Prompt'
|
||||
import {ViewHeader} from '../util/ViewHeader'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {isAndroid, isNative, isWeb} from '#/platform/detection'
|
||||
import {
|
||||
ThreadNode,
|
||||
ThreadPost,
|
||||
ThreadNotFound,
|
||||
ThreadBlocked,
|
||||
usePostThreadQuery,
|
||||
sortThread,
|
||||
ThreadBlocked,
|
||||
ThreadNode,
|
||||
ThreadNotFound,
|
||||
ThreadPost,
|
||||
usePostThreadQuery,
|
||||
} from '#/state/queries/post-thread'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {
|
||||
useModerationOpts,
|
||||
usePreferencesQuery,
|
||||
} from '#/state/queries/preferences'
|
||||
import {useSession} from '#/state/session'
|
||||
import {isAndroid, isNative, isWeb} from '#/platform/detection'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {ComposePrompt} from '../composer/Prompt'
|
||||
import {List, ListMethods} from '../util/List'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ViewHeader} from '../util/ViewHeader'
|
||||
import {PostThreadItem} from './PostThreadItem'
|
||||
|
||||
// FlatList maintainVisibleContentPosition breaks if too many items
|
||||
// are prepended. This seems to be an optimal number based on *shrug*.
|
||||
|
|
Loading…
Reference in New Issue