Rough sketch
This commit is contained in:
@@ -69,7 +69,7 @@ function LabelGroupButton({labelGroup}: {labelGroup: string}) {
|
|||||||
interacted && styles.interacted,
|
interacted && styles.interacted,
|
||||||
]}>
|
]}>
|
||||||
<View style={[a.flex_1, a.gap_xs]}>
|
<View style={[a.flex_1, a.gap_xs]}>
|
||||||
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_700]}>
|
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
|
||||||
{groupInfoStrings.name}
|
{groupInfoStrings.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[a.leading_tight, {maxWidth: 400}]}>
|
<Text style={[a.leading_tight, {maxWidth: 400}]}>
|
||||||
@@ -88,7 +88,7 @@ function LabelGroupButton({labelGroup}: {labelGroup: string}) {
|
|||||||
<ChevronRight
|
<ChevronRight
|
||||||
size="md"
|
size="md"
|
||||||
fill={
|
fill={
|
||||||
hovered ? t.palette.primary_500 : t.atoms.text_contrast_400.color
|
hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -135,7 +135,7 @@ function ModServiceToggle({title}: {title: string}) {
|
|||||||
fill={
|
fill={
|
||||||
ctx.selected
|
ctx.selected
|
||||||
? t.palette.primary_200
|
? t.palette.primary_200
|
||||||
: t.atoms.text_contrast_400.color
|
: t.atoms.text_contrast_low.color
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -204,7 +204,7 @@ function SubmitView({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={[a.gap_md]}>
|
<View style={[a.gap_md]}>
|
||||||
<Text style={[t.atoms.text_contrast_700]}>
|
<Text style={[t.atoms.text_contrast_medium]}>
|
||||||
Select the moderation service(s) to report to
|
Select the moderation service(s) to report to
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ function SubmitView({
|
|||||||
</Toggle.Group>
|
</Toggle.Group>
|
||||||
</View>
|
</View>
|
||||||
<View style={[a.gap_md]}>
|
<View style={[a.gap_md]}>
|
||||||
<Text style={[t.atoms.text_contrast_700]}>
|
<Text style={[t.atoms.text_contrast_medium]}>
|
||||||
Optionally provide additional information below:
|
Optionally provide additional information below:
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ export function ReportDialog({
|
|||||||
<View style={[a.gap_lg]}>
|
<View style={[a.gap_lg]}>
|
||||||
<View style={[a.justify_center, a.gap_sm]}>
|
<View style={[a.justify_center, a.gap_sm]}>
|
||||||
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
|
<Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
|
||||||
<Text style={[a.text_md, t.atoms.text_contrast_700]}>
|
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||||
{i18n.description}
|
{i18n.description}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import {useFocusEffect} from '@react-navigation/native'
|
|||||||
import {ComAtprotoLabelDefs} from '@atproto/api'
|
import {ComAtprotoLabelDefs} from '@atproto/api'
|
||||||
import {Trans, msg} from '@lingui/macro'
|
import {Trans, msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {
|
||||||
|
LABEL_GROUPS,
|
||||||
|
LabelGroupDefinition,
|
||||||
|
DEFAULT_LABEL_GROUP_SETTINGS,
|
||||||
|
} from '@atproto/api'
|
||||||
|
|
||||||
import {NativeStackScreenProps, CommonNavigatorParams} from '#/lib/routes/types'
|
import {NativeStackScreenProps, CommonNavigatorParams} from '#/lib/routes/types'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
@@ -22,18 +27,116 @@ import {Divider} from '#/components/Divider'
|
|||||||
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
|
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
|
||||||
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
||||||
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
|
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
|
||||||
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
|
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||||
import {InlineLink, Link} from '#/components/Link'
|
import {InlineLink, Link} from '#/components/Link'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {Button} from '#/components/Button'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>
|
function ModSettingsToggleItem({name}: {name: string}) {
|
||||||
export function ModerationScreen({}: Props) {
|
const t = useTheme()
|
||||||
|
const ctx = Toggle.useItemContext()
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.flex_row,
|
||||||
|
a.justify_between,
|
||||||
|
a.align_center,
|
||||||
|
a.p_md,
|
||||||
|
a.rounded_sm,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
{
|
||||||
|
backgroundColor: ctx.selected ? t.palette.primary_25 : undefined,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Text>{name}</Text>
|
||||||
|
{ctx.selected && <Check />}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModSettingsDialog({
|
||||||
|
name,
|
||||||
|
onComplete,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
onComplete: () => void
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const labelGroupStrings = useLabelGroupStrings()
|
||||||
|
const [selectedServices, setSelectedServices] = React.useState<string[]>([
|
||||||
|
'bluesky',
|
||||||
|
])
|
||||||
|
|
||||||
|
const save = React.useCallback(() => {
|
||||||
|
onComplete()
|
||||||
|
}, [onComplete])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Inner label="Configure moderation service settings">
|
||||||
|
<Text style={[a.text_xl, a.font_bold, a.pb_sm]}>
|
||||||
|
Configure moderation services
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_md,
|
||||||
|
a.leading_snug,
|
||||||
|
t.atoms.text_contrast_high,
|
||||||
|
a.pb_lg,
|
||||||
|
]}>
|
||||||
|
Select which moderation services' labels you'd like to use to filter
|
||||||
|
content matching {labelGroupStrings[name].name}.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Toggle.Group
|
||||||
|
values={selectedServices}
|
||||||
|
onChange={setSelectedServices}
|
||||||
|
label="Select one or more">
|
||||||
|
<View style={[a.w_full, a.gap_md]}>
|
||||||
|
<Toggle.Item name="bluesky" label="Bluesky">
|
||||||
|
<ModSettingsToggleItem name="Bluesky" />
|
||||||
|
</Toggle.Item>
|
||||||
|
<Toggle.Item name="contraption" label="Contraption">
|
||||||
|
<ModSettingsToggleItem name="Contraption" />
|
||||||
|
</Toggle.Item>
|
||||||
|
<Toggle.Item name="safety" label="Safety Corp">
|
||||||
|
<ModSettingsToggleItem name="Safety Corp" />
|
||||||
|
</Toggle.Item>
|
||||||
|
</View>
|
||||||
|
</Toggle.Group>
|
||||||
|
|
||||||
|
<View style={[a.flex_row, a.justify_end, a.pt_lg]}>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
label="Save"
|
||||||
|
onPress={save}>
|
||||||
|
{selectedServices.length ? 'Save' : 'Ignore all'}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Dialog.Inner>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ModerationScreen({}: NativeStackScreenProps<
|
||||||
|
CommonNavigatorParams,
|
||||||
|
'Moderation'
|
||||||
|
>) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {screen} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
const {gtMobile, gtTablet} = useBreakpoints()
|
const {gtMobile, gtTablet} = useBreakpoints()
|
||||||
|
const labelGroupStrings = useLabelGroupStrings()
|
||||||
|
const modSettingsDialogControl = Dialog.useDialogControl()
|
||||||
|
const [modSettingsDialogLabelGroup, setModSettingsDialogLabelGroup] =
|
||||||
|
React.useState<string>(() => Object.keys(LABEL_GROUPS)[0])
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
React.useCallback(() => {
|
React.useCallback(() => {
|
||||||
@@ -42,6 +145,25 @@ export function ModerationScreen({}: Props) {
|
|||||||
}, [screen, setMinimalShellMode]),
|
}, [screen, setMinimalShellMode]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const groups = React.useMemo<
|
||||||
|
[keyof typeof LABEL_GROUPS, LabelGroupDefinition][]
|
||||||
|
>(() => {
|
||||||
|
return Object.entries(LABEL_GROUPS).filter(([, def]) => def.configurable)
|
||||||
|
}, [])
|
||||||
|
const labelOptions = {
|
||||||
|
hide: _(msg`Hide`),
|
||||||
|
warn: _(msg`Warn`),
|
||||||
|
show: _(msg`Show`),
|
||||||
|
}
|
||||||
|
|
||||||
|
const openModSettingsDialog = React.useCallback(
|
||||||
|
({name}: {name: string}) => {
|
||||||
|
setModSettingsDialogLabelGroup(name)
|
||||||
|
modSettingsDialogControl.open()
|
||||||
|
},
|
||||||
|
[modSettingsDialogControl],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView
|
<CenteredView
|
||||||
style={[
|
style={[
|
||||||
@@ -51,6 +173,15 @@ export function ModerationScreen({}: Props) {
|
|||||||
...(gtMobile ? [a.border_l, a.border_r] : []),
|
...(gtMobile ? [a.border_l, a.border_r] : []),
|
||||||
]}
|
]}
|
||||||
testID="moderationScreen">
|
testID="moderationScreen">
|
||||||
|
<Dialog.Outer control={modSettingsDialogControl}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
|
||||||
|
<ModSettingsDialog
|
||||||
|
name={modSettingsDialogLabelGroup}
|
||||||
|
onComplete={() => modSettingsDialogControl.close()}
|
||||||
|
/>
|
||||||
|
</Dialog.Outer>
|
||||||
|
|
||||||
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
|
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
|
||||||
|
|
||||||
<ScrollView contentContainerStyle={[a.border_0]}>
|
<ScrollView contentContainerStyle={[a.border_0]}>
|
||||||
@@ -119,10 +250,132 @@ export function ModerationScreen({}: Props) {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
|
<View style={[a.gap_sm, a.px_lg, a.py_lg]}>
|
||||||
|
<Text style={[a.text_lg, a.font_bold, a.pb_sm]}>
|
||||||
|
<Trans>Content filtering settings</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{groups.map(([name, def], i) => {
|
||||||
|
const groupStrings = labelGroupStrings[name]
|
||||||
|
return (
|
||||||
|
<React.Fragment key={def.id}>
|
||||||
|
{i !== 0 && <Divider />}
|
||||||
|
|
||||||
|
<View style={[a.pb_md]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.py_md,
|
||||||
|
a.flex_row,
|
||||||
|
a.justify_between,
|
||||||
|
a.gap_sm,
|
||||||
|
a.align_center,
|
||||||
|
]}>
|
||||||
|
<View style={[a.gap_xs, {width: '50%'}]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_md,
|
||||||
|
a.font_bold,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
{groupStrings.name}
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.leading_tight, {maxWidth: 400}]}>
|
||||||
|
{groupStrings.description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<ToggleButton.Group
|
||||||
|
label={_(
|
||||||
|
msg`Configure content filtering setting for category: ${groupStrings.name.toLowerCase()}`,
|
||||||
|
)}
|
||||||
|
values={[DEFAULT_LABEL_GROUP_SETTINGS[name]]}
|
||||||
|
onChange={() => {}}>
|
||||||
|
<ToggleButton.Button
|
||||||
|
name="hide"
|
||||||
|
label={labelOptions.hide}>
|
||||||
|
{labelOptions.hide}
|
||||||
|
</ToggleButton.Button>
|
||||||
|
<ToggleButton.Button
|
||||||
|
name="warn"
|
||||||
|
label={labelOptions.warn}>
|
||||||
|
{labelOptions.warn}
|
||||||
|
</ToggleButton.Button>
|
||||||
|
<ToggleButton.Button
|
||||||
|
name="ignore"
|
||||||
|
label={labelOptions.show}>
|
||||||
|
{labelOptions.show}
|
||||||
|
</ToggleButton.Button>
|
||||||
|
</ToggleButton.Group>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.flex_row, a.align_start]}>
|
||||||
|
<Button
|
||||||
|
label={_(
|
||||||
|
msg`Configure moderation services for category: ${groupStrings.name.toLowerCase()}`,
|
||||||
|
)}
|
||||||
|
onPress={() =>
|
||||||
|
openModSettingsDialog({
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
<View style={[a.flex_row, a.align_start, a.gap_xs]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.py_sm,
|
||||||
|
a.px_md,
|
||||||
|
a.rounded_full,
|
||||||
|
t.atoms.bg_contrast_800,
|
||||||
|
]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_xs,
|
||||||
|
a.font_bold,
|
||||||
|
t.atoms.text_inverted,
|
||||||
|
]}>
|
||||||
|
Bluesky
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.py_sm,
|
||||||
|
a.px_md,
|
||||||
|
a.rounded_full,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
<Text style={[a.text_xs, a.font_bold]}>
|
||||||
|
Contraption
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.py_sm,
|
||||||
|
a.px_md,
|
||||||
|
a.rounded_full,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
<Text style={[a.text_xs, a.font_bold]}>
|
||||||
|
Safety Corp
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
<Text style={[a.text_lg, a.font_bold, a.pl_lg, a.pt_lg, a.pb_sm]}>
|
<Text style={[a.text_lg, a.font_bold, a.pl_lg, a.pt_lg, a.pb_sm]}>
|
||||||
<Trans>Logged-out visibility</Trans>
|
<Trans>Logged-out visibility</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<PwiOptOut />
|
<PwiOptOut />
|
||||||
|
|
||||||
|
<View style={{height: 200}} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user