Cleanup: delete old code

This commit is contained in:
Paul Frazee
2024-02-22 19:42:57 -08:00
parent 047df5939f
commit e3a4d800b4
11 changed files with 54 additions and 1464 deletions
-1
View File
@@ -211,7 +211,6 @@ func serve(cctx *cli.Context) error {
e.GET("/profile/:handleOrDID/lists/:rkey", server.WebGeneric)
e.GET("/profile/:handleOrDID/feed/:rkey", server.WebGeneric)
e.GET("/profile/:handleOrDID/feed/:rkey/liked-by", server.WebGeneric)
e.GET("/profile/:handleOrDID/modservice", server.WebGeneric)
// profile RSS feed (DID not handle)
e.GET("/profile/:ident/rss", server.WebProfileRSS)
-6
View File
@@ -56,7 +56,6 @@ import {ProfileFollowersScreen} from './view/screens/ProfileFollowers'
import {ProfileFollowsScreen} from './view/screens/ProfileFollows'
import {ProfileFeedScreen} from './view/screens/ProfileFeed'
import {ProfileFeedLikedByScreen} from './view/screens/ProfileFeedLikedBy'
import {ProfileModserviceScreen} from '#/screens/ProfileModerationService'
import {ProfileListScreen} from './view/screens/ProfileList'
import {PostThreadScreen} from './view/screens/PostThread'
import {PostLikedByScreen} from './view/screens/PostLikedBy'
@@ -198,11 +197,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => ProfileFeedLikedByScreen}
options={{title: title(msg`Liked by`)}}
/>
<Stack.Screen
name="ProfileModservice"
getComponent={() => ProfileModserviceScreen}
options={{title: title(msg`Moderation service`)}}
/>
<Stack.Screen
name="Debug"
getComponent={() => Storybook}
@@ -3,52 +3,26 @@ import {View} from 'react-native'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {LABEL_GROUPS} from '@atproto/api'
// TODO
import {ModPrefItem} from '@atproto/api/dist/client/types/app/bsky/actor/defs'
import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings'
import {useModServiceLabelGroupEnableMutation} from '#/state/queries/modservice'
import {logger} from '#/logger'
import {useTheme, atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
import * as ToggleButton from '#/components/forms/ToggleButton'
export function PreferenceRow({
export function ModerationLabelPref({
labelGroup,
disabled,
modservicePreferences,
}: {
labelGroup: keyof typeof LABEL_GROUPS
disabled?: boolean
modservicePreferences?: ModPrefItem
}) {
const t = useTheme()
const {_} = useLingui()
const labelGroupStrings = useLabelGroupStrings()
const groupInfoStrings = labelGroupStrings[labelGroup]
const {mutateAsync, variables} = useModServiceLabelGroupEnableMutation()
const enabled =
variables?.enabled ??
!modservicePreferences?.disabledLabelGroups?.includes(labelGroup)
const onToggleEnabled = React.useCallback(async () => {
try {
if (!modservicePreferences)
throw new Error(`modservicePreferences not found`)
await mutateAsync({
did: modservicePreferences.did,
group: labelGroup,
enabled: !enabled,
})
} catch (e: any) {
logger.error(`Failed to toggle label group enabled`, {
message: e.message,
labelGroup,
})
}
}, [mutateAsync, enabled, modservicePreferences, labelGroup])
// TODO add onChange behavior when mod prefs are updated
const labelOptions = {
hide: _(msg`Hide`),
@@ -62,7 +36,9 @@ export function PreferenceRow({
a.flex_row,
a.justify_between,
a.gap_sm,
a.py_xs,
a.py_md,
a.pl_lg,
a.pr_md,
a.align_center,
]}>
<View style={[a.gap_xs, {width: '50%'}]}>
@@ -72,7 +48,7 @@ export function PreferenceRow({
</Text>
</View>
<View style={[a.justify_center, {minHeight: 35}]}>
{modservicePreferences && (
{!disabled && (
<ToggleButton.Group
label={_(
msg`Configure content filtering setting for category: ${groupInfoStrings.name.toLowerCase()}`,
-1
View File
@@ -21,7 +21,6 @@ export type CommonNavigatorParams = {
PostRepostedBy: {name: string; rkey: string}
ProfileFeed: {name: string; rkey: string}
ProfileFeedLikedBy: {name: string; rkey: string}
ProfileModservice: {name: string}
Debug: undefined
DebugMod: undefined
Log: undefined
-1
View File
@@ -21,7 +21,6 @@ export const router = new Router({
PostRepostedBy: '/profile/:name/post/:rkey/reposted-by',
ProfileFeed: '/profile/:name/feed/:rkey',
ProfileFeedLikedBy: '/profile/:name/feed/:rkey/liked-by',
ProfileModservice: '/profile/:name/modservice',
Debug: '/sys/debug',
DebugMod: '/sys/debug-mod',
Log: '/sys/log',
-190
View File
@@ -1,190 +0,0 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyModerationDefs} from '@atproto/api'
import {LabelGroupDefinition} from '@atproto/api'
import {Trans} from '@lingui/macro'
import {useTheme, atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
import * as Toggle from '#/components/forms/Toggle'
import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings'
import * as Dialog from '#/components/Dialog'
import * as ModerationServiceCard from '#/components/ModerationServiceCard'
import {getModerationServiceTitle} from '#/lib/moderation'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences'
import {useModServiceLabelGroupEnableMutation} from '#/state/queries/modservice'
import {Divider} from '#/components/Divider'
import {InlineLink} from '#/components/Link'
import {useDialogStateControlContext} from '#/state/dialogs'
import {logger} from '#/logger'
function LabelerToggle({
labelGroup,
labeler,
preferences,
}: {
labelGroup: LabelGroupDefinition['id']
labeler: AppBskyModerationDefs.ModServiceViewDetailed
preferences: UsePreferencesQueryResponse
}) {
const t = useTheme()
const {
mutateAsync: toggleGroupEnabled,
variables: optimisticToggleGroupEnabled,
} = useModServiceLabelGroupEnableMutation()
const {closeAllDialogs} = useDialogStateControlContext()
const labelerPrefs = React.useMemo(
() =>
preferences.moderationOpts.mods.find(
({did}) => did === labeler.creator.did,
),
[preferences.moderationOpts.mods, labeler.creator.did],
)
const isLabelerEnabled = !!labelerPrefs?.enabled
const isEnabled = isLabelerEnabled
? optimisticToggleGroupEnabled?.enabled ??
!labelerPrefs?.disabledLabelGroups?.includes(labelGroup)
: false
const title = getModerationServiceTitle({
displayName: labeler.creator.displayName,
handle: labeler.creator.handle,
})
const onToggleEnabled = React.useCallback(async () => {
try {
if (!labelerPrefs) throw new Error(`labelerPrefs not found`)
await toggleGroupEnabled({
did: labelerPrefs.did,
group: labelGroup,
enabled: !isEnabled,
})
} catch (e: any) {
logger.error(`Failed to toggle label group enabled`, {
message: e.message,
labelGroup,
})
}
}, [toggleGroupEnabled, isEnabled, labelerPrefs, labelGroup])
return (
<View style={[a.rounded_sm, a.border, t.atoms.border_contrast_low]}>
<View style={[a.flex_row, a.justify_between, a.p_md]}>
<View style={[a.flex_row, a.align_center, a.gap_md]}>
<ModerationServiceCard.Card.Avatar avatar={labeler.creator.avatar} />
<View>
<ModerationServiceCard.Card.Title value={title} />
<ModerationServiceCard.Card.Description
value={labeler.description}
handle={labeler.creator.handle}
/>
</View>
</View>
<Toggle.Item
disabled={!isLabelerEnabled}
name={labeler.creator.did}
label={title}
key={labeler.creator.did}
value={isEnabled}
onChange={onToggleEnabled}>
<Toggle.Label>{isEnabled ? 'Enabled' : 'Disabled'}</Toggle.Label>
<Toggle.Switch />
</Toggle.Item>
</View>
<Divider />
<View style={[a.flex_row, a.p_md]}>
<Text style={[a.text_xs]}>
Configure more settings for this labeler{' '}
<InlineLink
to={{
screen: 'ProfileModservice',
params: {
name: labeler.creator.handle,
},
}}
style={[a.text_xs]}
onPress={closeAllDialogs}>
here.
</InlineLink>
</Text>
</View>
</View>
)
}
export type SettingsDialogProps = {
labelGroup: LabelGroupDefinition['id']
modservices: AppBskyModerationDefs.ModServiceViewDetailed[]
}
export function SettingsDialog({
labelGroup,
modservices,
preferences,
}: SettingsDialogProps & {
preferences: UsePreferencesQueryResponse
}) {
const t = useTheme()
const labelGroupStrings = useLabelGroupStrings()
const groupInfoStrings = labelGroupStrings[labelGroup]
// this is mounted on native
if (!groupInfoStrings) return null
return (
<Dialog.ScrollableInner label="Configure moderation service settings">
<Dialog.Close />
<Text style={[a.text_xl, a.font_bold, a.pb_xl]}>
<Trans>Configure enabled labelers</Trans>
</Text>
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_between,
a.gap_lg,
a.p_md,
a.rounded_md,
a.mb_xl,
t.atoms.bg_contrast_25,
]}>
<View style={[a.flex_1, a.gap_xs]}>
<Text style={[a.font_bold]}>{groupInfoStrings.name}</Text>
<Text style={[a.leading_tight, {maxWidth: 400}]}>
{groupInfoStrings.description}
</Text>
</View>
</View>
<Text
style={[
a.leading_snug,
a.font_bold,
t.atoms.text_contrast_medium,
a.pb_lg,
]}>
<Trans>Select which labelers to use for this type of content:</Trans>
</Text>
<View style={[a.w_full, a.gap_md]}>
{modservices.map(modservice => {
return (
<LabelerToggle
key={modservice.creator.did}
labelGroup={labelGroup}
labeler={modservice}
preferences={preferences}
/>
)
})}
</View>
</Dialog.ScrollableInner>
)
}
+29 -532
View File
@@ -1,10 +1,10 @@
import React from 'react'
import {View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {ComAtprotoLabelDefs, LabelPreference, LABELS} from '@atproto/api'
import {ComAtprotoLabelDefs} from '@atproto/api'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {LabelGroupDefinition, AppBskyModerationDefs} from '@atproto/api'
import {AppBskyModerationDefs} from '@atproto/api'
import {useSafeAreaFrame} from 'react-native-safe-area-context'
import {NativeStackScreenProps, CommonNavigatorParams} from '#/lib/routes/types'
@@ -18,43 +18,27 @@ import {
useProfileUpdateMutation,
} from '#/state/queries/profile'
import {ScrollView} from '#/view/com/util/Views'
import {logger} from '#/logger'
import {
UsePreferencesQueryResponse,
usePreferencesQuery,
useSetContentLabelMutation,
usePreferencesSetAdultContentMutation,
} from '#/state/queries/preferences'
import {useModServicesDetailedInfoQuery} from '#/state/queries/modservice'
import {useModServiceLabelGroupEnableMutation} from '#/state/queries/modservice'
import {useTheme, atoms as a, useBreakpoints, native} from '#/alf'
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
import {Divider} from '#/components/Divider'
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
import {Text} from '#/components/Typography'
import * as Toggle from '#/components/forms/Toggle'
import * as ToggleButton from '#/components/forms/ToggleButton'
import {InlineLink, Link} from '#/components/Link'
import {Loader} from '#/components/Loader'
import {useLabelGroupStrings} from '#/lib/moderation/useLabelGroupStrings'
import * as Dialog from '#/components/Dialog'
import {Button} from '#/components/Button'
import {
getModerationServiceTitle,
useConfigurableLabelGroups,
getLabelGroupToLabelerMap,
} from '#/lib/moderation'
import {getModerationServiceTitle} from '#/lib/moderation'
import * as ModerationServiceCard from '#/components/ModerationServiceCard'
import {
SettingsDialog,
SettingsDialogProps,
} from '#/screens/Moderation/SettingsDialog'
import {ModerationLabelPref} from '#/components/ModerationLabelPref'
function ErrorState({error}: {error: string}) {
const t = useTheme()
@@ -153,17 +137,6 @@ export function ModerationScreenInner({
const setMinimalShellMode = useSetMinimalShellMode()
const {screen} = useAnalytics()
const {gtMobile} = useBreakpoints()
const modSettingsDialogControl = Dialog.useDialogControl()
const [settingsDialogProps, setSettingsDialogProps] =
React.useState<SettingsDialogProps>({
// prefill with valid value to appease TS
labelGroup: 'intolerance',
modservices: [],
})
const groups = useConfigurableLabelGroups()
const labelGroupToLabelerMap = React.useMemo(() => {
return getLabelGroupToLabelerMap(modservices)
}, [modservices])
useFocusEffect(
React.useCallback(() => {
@@ -172,17 +145,6 @@ export function ModerationScreenInner({
}, [screen, setMinimalShellMode]),
)
const openModSettingsDialog = React.useCallback(
({labelGroup, modservices}: Omit<SettingsDialogProps, 'onComplete'>) => {
setSettingsDialogProps({
labelGroup,
modservices,
})
modSettingsDialogControl.open()
},
[modSettingsDialogControl],
)
const {mutateAsync: setAdultContentPref, variables: optimisticAdultContent} =
usePreferencesSetAdultContentMutation()
const adultContentEnabled = !!(
@@ -206,11 +168,6 @@ export function ModerationScreenInner({
return (
<View>
<Dialog.Outer control={modSettingsDialogControl}>
<Dialog.Handle />
<SettingsDialog {...settingsDialogProps} preferences={preferences} />
</Dialog.Outer>
<ScrollView
contentContainerStyle={[
a.border_0,
@@ -340,65 +297,15 @@ export function ModerationScreenInner({
{adultContentEnabled && (
<>
<Divider />
<LabelGroup
labelGroup={groups.find(g => g.id === 'porn')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
<ModerationLabelPref labelGroup="porn" />
<Divider />
<LabelGroup
labelGroup={groups.find(g => g.id === 'suggestive')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
<ModerationLabelPref labelGroup="suggestive" />
<Divider />
<LabelGroup
labelGroup={groups.find(g => g.id === 'nudity')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
<ModerationLabelPref labelGroup="nudity" />
<Divider />
<LabelGroup
labelGroup={groups.find(g => g.id === 'violence')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
<ModerationLabelPref labelGroup="violence" />
</>
)}
<Divider />
<View
style={[a.pt_lg, a.pb_md, a.px_lg, a.flex_row, a.justify_between]}>
<View style={[a.flex_wrap, a.flex_1]}>
<Text
style={[
a.text_md,
a.font_semibold,
t.atoms.text_contrast_high,
a.pb_xs,
]}>
<Trans>Hide posts by bots</Trans>
</Text>
<Text style={[t.atoms.text_contrast_medium]}>
<Trans>Bots are automated users which follow the rules.</Trans>
</Text>
</View>
<Toggle.Item
label={'Toggle hiding posts by bots'}
name="hideBots"
value={false}
onChange={undefined}>
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
<Text style={[t.atoms.text_contrast_medium]}>
{false ? <Trans>Enabled</Trans> : <Trans>Disabled</Trans>}
</Text>
<Toggle.Switch />
</View>
</Toggle.Item>
</View>
</View>
<Text
@@ -436,184 +343,32 @@ export function ModerationScreenInner({
/>
</ModerationServiceCard.Card.Outer>
</ModerationServiceCard.Link>
<Divider />
{modservices.map(mod => {
return (
<ModerationServiceCard.Link
modservice={mod}
key={mod.creator.did}>
<ModerationServiceCard.Card.Outer>
<ModerationServiceCard.Card.Avatar
avatar={mod.creator.avatar}
/>
<ModerationServiceCard.Card.Content
title={getModerationServiceTitle({
displayName: mod.creator.displayName,
handle: mod.creator.handle,
})}
handle={mod.creator.handle}
description={mod.description}
/>
</ModerationServiceCard.Card.Outer>
</ModerationServiceCard.Link>
<>
<Divider />
<ModerationServiceCard.Link
modservice={mod}
key={mod.creator.did}>
<ModerationServiceCard.Card.Outer>
<ModerationServiceCard.Card.Avatar
avatar={mod.creator.avatar}
/>
<ModerationServiceCard.Card.Content
title={getModerationServiceTitle({
displayName: mod.creator.displayName,
handle: mod.creator.handle,
})}
handle={mod.creator.handle}
description={mod.description}
/>
</ModerationServiceCard.Card.Outer>
</ModerationServiceCard.Link>
</>
)
})}
</View>
{
undefined /*<Text
style={[
a.text_md,
a.font_bold,
a.pt_2xl,
a.pb_md,
t.atoms.text_contrast_high,
]}>
<Trans>Bluesky Safety</Trans>{' '}
<Text style={[a.text_sm, a.pb_md, t.atoms.text_contrast_medium]}>
@safety.bsky.app
</Text>
</Text>
<View
style={[
a.w_full,
a.rounded_md,
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
<LabelGroup
labelGroup={groups.find(g => g.id === 'intolerance')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
<Divider />
<LabelGroup
labelGroup={groups.find(g => g.id === 'misrepresentation')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
<Divider />
<LabelGroup
labelGroup={groups.find(g => g.id === 'security')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
<Divider />
<LabelGroup
labelGroup={groups.find(g => g.id === 'spam')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
</View>
{modservices.map(mod => {
return (
<>
<Text
style={[
a.text_md,
a.font_bold,
a.pt_2xl,
a.pb_md,
t.atoms.text_contrast_high,
]}>
{mod.creator.displayName}{' '}
<Text
style={[a.text_sm, a.pb_md, t.atoms.text_contrast_medium]}>
@{mod.creator.handle}
</Text>
</Text>
<View
style={[
a.w_full,
a.rounded_md,
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
{mod.policies.labelValues
.filter(label => !label.startsWith('!'))
.map((label, i) => {
return (
<React.Fragment key={mod.did + '-' + label}>
{i !== 0 && <Divider />}
<LabelGroup
labelGroup={LABELS[label].groupId}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
</React.Fragment>
)
})}
</View>
</>
)
})}
<Text
style={[
a.text_md,
a.font_bold,
a.pt_2xl,
a.pb_md,
t.atoms.text_contrast_high,
]}>
<Trans>Other</Trans>
</Text>
<View
style={[
a.w_full,
a.rounded_md,
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
<LabelGroup
labelGroup={groups.find(g => g.id === 'bot')!.id}
labelers={[]}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
</View>
<Text
style={[
a.text_md,
a.font_bold,
a.pt_2xl,
a.pb_md,
t.atoms.text_contrast_high,
]}>
<Trans>Content filtering</Trans>
</Text>
<View
style={[
a.w_full,
a.rounded_md,
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
{groups.map((def, i) => {
const labelers = labelGroupToLabelerMap[def.id] || []
return (
<React.Fragment key={def.id}>
{i !== 0 && <Divider />}
<LabelGroup
labelGroup={def.id}
labelers={labelers}
preferences={preferences}
openModSettingsDialog={openModSettingsDialog}
/>
</React.Fragment>
)
})}
</View>*/
}
<Text
style={[
a.text_md,
@@ -633,264 +388,6 @@ export function ModerationScreenInner({
)
}
function LabelGroup({
labelGroup,
labelers: mods,
preferences,
}: // openModSettingsDialog,
{
labelGroup: LabelGroupDefinition['id']
labelers: AppBskyModerationDefs.ModServiceViewDetailed[]
preferences: UsePreferencesQueryResponse
openModSettingsDialog: (props: SettingsDialogProps) => void
}) {
const t = useTheme()
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const labelGroupStrings = useLabelGroupStrings()
const {mutateAsync: setContentLabelPref, variables: optimisticContentLabel} =
useSetContentLabelMutation()
const [settingsOpen, setSettingsOpen] = React.useState(false)
const onChangeVisibility = React.useCallback(
async (values: string[]) => {
try {
await setContentLabelPref({
labelGroup,
visibility: values[0] as LabelPreference,
})
} catch (e) {
// TODO
console.error(e)
}
},
[labelGroup, setContentLabelPref],
)
const {name, description} = labelGroupStrings[labelGroup]
const value =
optimisticContentLabel?.visibility ??
preferences.moderationOpts.labelGroups[labelGroup]
const labelOptions = {
hide: _(msg`Hide`),
warn: _(msg`Warn`),
// show: _(msg`Show`),
}
return (
<View style={[a.px_lg, a.pr_md, gtMobile && a.px_lg]}>
<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.font_semibold, t.atoms.text_contrast_high]}>
{name}
</Text>
<Text style={[a.leading_tight, {maxWidth: 400}]}>{description}</Text>
</View>
<View>
<ToggleButton.Group
label={_(
msg`Configure content filtering setting for category: ${name.toLowerCase()}`,
)}
values={[value]}
onChange={onChangeVisibility}>
<ToggleButton.Button name="warn" label={labelOptions.warn}>
{labelOptions.warn}
</ToggleButton.Button>
<ToggleButton.Button name="hide" label={labelOptions.hide}>
{labelOptions.hide}
</ToggleButton.Button>
{
undefined /*<ToggleButton.Button name="ignore" label={labelOptions.show}>
{labelOptions.show}
</ToggleButton.Button>*/
}
</ToggleButton.Group>
</View>
</View>
{!!mods.length && (
<View style={[a.mb_md, a.rounded_sm, t.atoms.bg]}>
<Button
label="Expand to configure labelers"
onPress={() => setSettingsOpen(!settingsOpen)}>
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_between,
a.p_md,
]}>
<View style={[a.flex_row, a.align_center, a.flex_1, a.gap_xs]}>
{mods.map(mod => {
const modservicePreferences =
preferences.moderationOpts.mods.find(
({did}) => did === mod.creator.did,
)
const labelGroupEnabled =
!modservicePreferences?.disabledLabelGroups?.includes(
labelGroup,
)
const isLabelerEnabled = modservicePreferences?.enabled
const enabled = labelGroupEnabled && isLabelerEnabled
return (
<View
key={mod.creator.did}
style={[
a.flex_row,
a.align_center,
a.gap_xs,
a.py_xs,
a.px_sm,
a.rounded_xs,
t.atoms.bg_contrast_600,
enabled && t.atoms.bg_contrast_800,
]}>
<View
style={[
a.rounded_full,
t.atoms.bg_contrast_300,
{
height: 6,
width: 6,
},
enabled && {
backgroundColor: t.palette.positive_500,
},
]}
/>
<Text
style={[
a.text_xs,
a.font_bold,
native({
top: 1,
}),
t.atoms.text_inverted,
]}>
{getModerationServiceTitle({
displayName: mod.creator.displayName,
handle: mod.creator.handle,
})}
</Text>
</View>
)
})}
</View>
<ChevronDown size="sm" />
</View>
</Button>
{settingsOpen && (
<View style={[a.px_md]}>
<Divider />
{mods.map((mod, i) => {
return (
<React.Fragment key={mod.creator.did}>
{i !== 0 && <Divider />}
<LabelerToggle
labelGroup={labelGroup}
labeler={mod}
preferences={preferences}
/>
</React.Fragment>
)
})}
</View>
)}
</View>
)}
</View>
)
}
function LabelerToggle({
labelGroup,
labeler,
preferences,
}: {
labelGroup: LabelGroupDefinition['id']
labeler: AppBskyModerationDefs.ModServiceViewDetailed
preferences: UsePreferencesQueryResponse
}) {
const t = useTheme()
const {
mutateAsync: toggleGroupEnabled,
variables: optimisticToggleGroupEnabled,
} = useModServiceLabelGroupEnableMutation()
const labelerPrefs = React.useMemo(
() =>
preferences.moderationOpts.mods.find(
({did}) => did === labeler.creator.did,
),
[preferences.moderationOpts.mods, labeler.creator.did],
)
const isLabelerEnabled = !!labelerPrefs?.enabled
const isEnabled = isLabelerEnabled
? optimisticToggleGroupEnabled?.enabled ??
!labelerPrefs?.disabledLabelGroups?.includes(labelGroup)
: false
const title = getModerationServiceTitle({
displayName: labeler.creator.displayName,
handle: labeler.creator.handle,
})
const onToggleEnabled = React.useCallback(async () => {
try {
if (!labelerPrefs) throw new Error(`labelerPrefs not found`)
await toggleGroupEnabled({
did: labelerPrefs.did,
group: labelGroup,
enabled: !isEnabled,
})
} catch (e: any) {
logger.error(`Failed to toggle label group enabled`, {
message: e.message,
labelGroup,
})
}
}, [toggleGroupEnabled, isEnabled, labelerPrefs, labelGroup])
return (
<Toggle.Item
disabled={!isLabelerEnabled}
label={'Toggle label'}
name={labeler.creator.did}
value={isEnabled}
onChange={onToggleEnabled}>
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_between,
a.py_md,
]}>
<Text style={[a.font_bold]}>
{title}{' '}
<Text style={[t.atoms.text_contrast_low]}>
{isLabelerEnabled ? '' : '(labeler disabled)'}
</Text>
</Text>
<Toggle.Switch />
</View>
</Toggle.Item>
)
}
function PwiOptOut() {
const t = useTheme()
const {_} = useLingui()
+19 -112
View File
@@ -5,26 +5,16 @@ import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSafeAreaFrame} from 'react-native-safe-area-context'
import * as Toast from '#/view/com/util/Toast'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {Haptics} from '#/lib/haptics'
import {useAnalytics} from '#/lib/analytics/analytics'
import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {logger} from '#/logger'
import {useModServiceEnableMutation} from '#/state/queries/modservice'
import {useSession} from '#/state/session'
import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
import {getLabelGroupsFromLabels} from '#/lib/moderation'
import {useTheme, atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
import {Loader} from '#/components/Loader'
import {Divider} from '#/components/Divider'
import * as Toggle from '#/components/forms/Toggle'
import {ErrorState} from '#/screens/ProfileModerationService/ErrorState'
import {PreferenceRow} from '#/screens/ProfileModerationService/PreferenceRow'
import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {ErrorState} from '../ErrorState'
import {ModerationLabelPref} from '#/components/ModerationLabelPref'
export function ProfileContentFiltersSection({
modServiceQuery,
@@ -83,77 +73,19 @@ export function ProfileContentFiltersSectionInner({
modservice: AppBskyModerationDefs.ModServiceViewDetailed
}) {
const t = useTheme()
const {_} = useLingui()
const {hasSession} = useSession()
const {track} = useAnalytics()
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
useUnlikeMutation()
const {mutateAsync: toggleEnabled, variables: enabledVariables} =
useModServiceEnableMutation()
const [likeUri, setLikeUri] = React.useState<string>(
modservice.viewer?.like || '',
)
// TODO error state
const [_enablementError, setEnablementError] = React.useState<string>('')
const isLiked = !!likeUri
const groups = React.useMemo(() => {
return getLabelGroupsFromLabels(modservice.policies.labelValues).filter(
def => def.configurable,
)
}, [modservice.policies.labelValues])
const modservicePreferences = React.useMemo(() => {
return moderationOpts.mods.find(p => p.did === modservice.creator.did)
}, [modservice.creator.did, moderationOpts.mods])
const isSubscribed = moderationOpts.mods.find(
mod => mod.did === modservice.creator.did,
)
const isEnabled = Boolean(
enabledVariables?.enabled ??
moderationOpts.mods.find(
mod => mod.did === modservice.creator.did && mod.enabled,
),
moderationOpts.mods.find(
mod => mod.did === modservice.creator.did && mod.enabled,
),
)
useSetTitle(modservice.creator.displayName || modservice.creator.handle)
const onToggleLiked = React.useCallback(async () => {
try {
Haptics.default()
if (isLiked && likeUri) {
await unlikeMod({uri: likeUri})
track('CustomFeed:Unlike')
setLikeUri('')
} else {
const res = await likeMod({uri: modservice.uri, cid: modservice.cid})
track('CustomFeed:Like')
setLikeUri(res.uri)
}
} catch (e: any) {
Toast.show(
_(
msg`There was an an issue contacting the server, please check your internet connection and try again.`,
),
)
logger.error(`Failed to toggle labeler like`, {message: e.message})
}
}, [likeUri, isLiked, modservice, likeMod, unlikeMod, track, _])
const onToggleLabelerEnabled = React.useCallback(async () => {
try {
await toggleEnabled({
did: modservice.creator.did,
enabled: !isEnabled,
})
} catch (e: any) {
setEnablementError(e.message)
logger.error(`Failed to toggle labeler enabled`, {message: e.message})
}
}, [toggleEnabled, isEnabled, modservice.creator.did])
return (
<ScrollView
scrollEventThrottle={1}
@@ -161,56 +93,31 @@ export function ProfileContentFiltersSectionInner({
borderWidth: 0,
paddingHorizontal: a.px_xl.paddingLeft,
}}>
{false /* TODO wanted? isSubscribed*/ ? (
<View style={[a.flex_row, a.pr_lg, a.pt_xl]}>
<View style={[a.gap_sm, a.flex_1]}>
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
Enable or disable labels from this service.
</Text>
</View>
<Toggle.Item
name="enable"
value={isEnabled}
onChange={onToggleLabelerEnabled}
label={
isEnabled
? _(msg`Disable this moderation service`)
: _(msg`Enable this moderation service`)
}>
<Toggle.Label>{isEnabled ? 'Enabled' : 'Disabled'}</Toggle.Label>
<Toggle.Switch />
</Toggle.Item>
</View>
) : (
<View style={[a.gap_sm, a.pt_xl]}>
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
This service labels the following types of content.
</Text>
</View>
)}
<Text
style={[
a.pt_xl,
t.atoms.text_contrast_high,
a.leading_snug,
a.text_md,
]}>
<Trans>This service labels the following types of content.</Trans>
</Text>
<View
style={[
a.gap_md,
a.mt_xl,
t.atoms.bg_contrast_25,
a.rounded_md,
a.border,
a.py_md,
t.atoms.border_contrast_low,
]}>
{groups.map((def, i) => {
return (
<React.Fragment key={def.id}>
{i !== 0 && <Divider />}
<View style={[a.px_lg]}>
<PreferenceRow
disabled={isEnabled ? undefined : true}
labelGroup={def.id}
modservicePreferences={modservicePreferences}
/>
</View>
<ModerationLabelPref
disabled={isEnabled ? undefined : true}
labelGroup={def.id}
/>
</React.Fragment>
)
})}
@@ -1,277 +0,0 @@
import React from 'react'
import {View} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {useNavigation} from '@react-navigation/native'
import {AppBskyModerationDefs} from '@atproto/api'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
import {sanitizeHandle} from '#/lib/strings/handles'
import {makeProfileLink} from '#/lib/routes/links'
import {NavigationProp} from '#/lib/routes/types'
import {useSetDrawerOpen} from '#/state/shell'
import {emitSoftReset} from '#/state/events'
import {shareUrl} from '#/lib/sharing'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {
NativeDropdown,
DropdownItem,
} from '#/view/com/util/forms/NativeDropdown'
import {useSession} from '#/state/session'
import {useModalControls} from '#/state/modals'
import {useModServiceSubscriptionMutation} from '#/state/queries/modservice'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences'
import {logger} from '#/logger'
import {useTheme, atoms as a, tokens, web, native, useBreakpoints} from '#/alf'
import {Text} from '#/components/Typography'
import {Button, ButtonText, ButtonIcon} from '#/components/Button'
import {RaisingHande4Finger_Stroke2_Corner0_Rounded as RaisingHand} from '#/components/icons/RaisingHand'
import {DotGrid1x3Horizontal_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
import {Bars3_Stroke2_Corner0_Rounded as Bars} from '#/components/icons/Bars'
import {Divider} from '#/components/Divider'
export function Header({
preferences,
modservice,
}: {
preferences: UsePreferencesQueryResponse
modservice: AppBskyModerationDefs.ModServiceViewDetailed
}) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>()
const {_} = useLingui()
const canGoBack = navigation.canGoBack()
const {mutateAsync: toggleSubscription, variables} =
useModServiceSubscriptionMutation()
const isSubscribed =
variables?.subscribe ??
preferences.moderationOpts.mods.find(
mod => mod.did === modservice.creator.did,
)
const [subscriptionError, setSubscriptionError] = React.useState<string>('')
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
}, [navigation])
const onPressMenu = React.useCallback(() => {
setDrawerOpen(true)
}, [setDrawerOpen])
const onPressSubscribe = React.useCallback(async () => {
try {
await toggleSubscription({
did: modservice.creator.did,
subscribe: !isSubscribed,
})
} catch (e: any) {
setSubscriptionError(e.message)
logger.error(`Failed to subscribe to labeler`, {message: e.message})
}
}, [toggleSubscription, isSubscribed, modservice.creator.did])
return (
<View style={[a.pb_xl, web(a.pt_md), gtMobile && web(a.pt_2xl)]}>
{!gtMobile && (
<View style={[a.mb_xl]}>
<View
style={[a.flex_row, a.justify_between, a.align_center, a.pb_md]}>
<Button
testID="headerDrawerBtn"
size="small"
color="secondary"
variant="solid"
shape="square"
onPress={canGoBack ? onPressBack : onPressMenu}
label={canGoBack ? _(msg`Go back`) : _(msg`Open main menu`)}>
<ButtonIcon icon={canGoBack ? ChevronLeft : Bars} />
</Button>
<View style={[a.flex_row, a.align_center, a.gap_md]}>
<Button
size="small"
variant="solid"
color="primary"
label={_(msg`Subscribe to this moderation service`)}
onPress={onPressSubscribe}>
<ButtonText>
{isSubscribed ? (
<Trans>Unsubscribe</Trans>
) : (
<Trans>Subscribe</Trans>
)}
</ButtonText>
</Button>
<CommonControls modservice={modservice} />
</View>
</View>
<Divider />
</View>
)}
<View style={[a.flex_row, a.gap_lg, a.align_start]}>
<View
style={[
a.p_md,
t.atoms.bg_contrast_50,
a.rounded_md,
a.overflow_hidden,
]}>
<LinearGradient
colors={tokens.gradients.midnight.values.map(c => c[1])}
locations={tokens.gradients.midnight.values.map(c => c[0])}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[a.absolute, a.inset_0]}
/>
<RaisingHand width={32} fill={t.palette.white} style={[a.z_10]} />
</View>
<View style={[a.flex_1, a.gap_xs, native(a.pt_2xs)]}>
<Button
label={_(msg`Refresh page`)}
testID="headerTitle"
// TODO do we need this?
onPress={emitSoftReset}
style={[a.justify_start]}>
<Text style={[a.text_4xl, a.font_bold, t.atoms.text]}>
{modservice.creator.displayName
? sanitizeDisplayName(modservice.creator.displayName)
: sanitizeHandle(modservice.creator.handle, '@')}
</Text>
</Button>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
<Trans>Moderation service</Trans>
</Text>
</View>
{gtMobile && (
<View style={[a.flex_row, a.align_center, a.gap_md]}>
<Button
size="small"
variant="solid"
color="primary"
label={
isSubscribed
? _(msg`Unsubscribe from this moderation service`)
: _(msg`Subscribe to this moderation service`)
}
onPress={onPressSubscribe}>
<ButtonText>
{isSubscribed ? (
<Trans>Unsubscribe</Trans>
) : (
<Trans>Subscribe</Trans>
)}
</ButtonText>
</Button>
<CommonControls modservice={modservice} />
</View>
)}
</View>
{subscriptionError && (
<View style={[a.pt_lg]}>
<View
style={[
a.px_lg,
a.py_md,
a.rounded_sm,
a.border,
a.gap_xs,
{
backgroundColor: t.palette.negative_50,
borderColor: t.palette.negative_100,
},
]}>
<Text style={[a.font_bold, {color: t.palette.negative_950}]}>
<Trans>An error occurred while editing your subscription.</Trans>
</Text>
<Text style={[{color: t.palette.negative_900}]}>
{subscriptionError || enablementError}
</Text>
</View>
</View>
)}
</View>
)
}
function CommonControls({
modservice,
}: {
modservice: AppBskyModerationDefs.ModServiceViewDetailed
}) {
const t = useTheme()
const {_} = useLingui()
const {hasSession} = useSession()
const {openModal} = useModalControls()
const onPressShare = React.useCallback(() => {
const url = makeProfileLink(modservice.creator, 'modservice')
shareUrl(url)
// track('CustomFeed:Share') TODO
}, [modservice /*, track*/])
const onPressReport = React.useCallback(() => {
if (!modservice) return
openModal({
name: 'report',
uri: modservice.uri,
cid: modservice.cid,
})
}, [openModal, modservice])
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={[a.rounded_full, a.p_sm, t.atoms.bg_contrast_50]}>
<Ellipsis width={20} fill={t.atoms.text_contrast_medium.color} />
</View>
</NativeDropdown>
)
}
@@ -1,314 +0,0 @@
import React from 'react'
import {View} from 'react-native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {AppBskyModerationDefs} from '@atproto/api'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSafeAreaFrame} from 'react-native-safe-area-context'
import {CommonNavigatorParams} from '#/lib/routes/types'
import * as Toast from '#/view/com/util/Toast'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {Haptics} from '#/lib/haptics'
import {useAnalytics} from '#/lib/analytics/analytics'
import {pluralize} from '#/lib/strings/helpers'
import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {logger} from '#/logger'
import {
useModServiceInfoQuery,
useModServiceEnableMutation,
} from '#/state/queries/modservice'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {
UsePreferencesQueryResponse,
usePreferencesQuery,
} from '#/state/queries/preferences'
import {useSession} from '#/state/session'
import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
import {getLabelGroupsFromLabels} from '#/lib/moderation'
import {useTheme, atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
import {Loader} from '#/components/Loader'
import {Button} from '#/components/Button'
import {
Heart2_Stroke2_Corner0_Rounded as Heart,
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
} from '#/components/icons/Heart2'
import {RichText} from '#/components/RichText'
import {InlineLink} from '#/components/Link'
import {Divider} from '#/components/Divider'
import * as Toggle from '#/components/forms/Toggle'
import {ErrorState} from '#/screens/ProfileModerationService/ErrorState'
import {Header} from '#/screens/ProfileModerationService/Header'
import {PreferenceRow} from '#/screens/ProfileModerationService/PreferenceRow'
export function ProfileModserviceScreen(
props: NativeStackScreenProps<CommonNavigatorParams, 'ProfileModservice'>,
) {
const t = useTheme()
const {_} = useLingui()
const {height: minHeight} = useSafeAreaFrame()
const {name: handleOrDid} = props.route.params
const {
isLoading: isDidResolutionLoading,
error: didResolutionError,
data: did,
} = useResolveDidQuery(handleOrDid)
const {
isLoading: isPreferencesLoading,
error: preferencesError,
data: preferences,
} = usePreferencesQuery()
const {
isLoading: isModServiceLoading,
error: modServiceError,
data: modservice,
} = useModServiceInfoQuery({did})
const isLoading =
isDidResolutionLoading || isPreferencesLoading || isModServiceLoading
const error = didResolutionError || preferencesError || modServiceError
return (
<CenteredView>
<View
style={[
a.border_l,
a.border_r,
t.atoms.border_contrast_low,
{
minHeight,
},
]}>
{isLoading ? (
<View style={[a.w_full, a.align_center]}>
<Loader size="xl" />
</View>
) : error || !(did && preferences && modservice) ? (
<ErrorState
error={
error?.toString() ||
_(msg`Something went wrong, please try again.`)
}
/>
) : (
<ProfileModserviceScreenInner
preferences={preferences}
modservice={modservice}
/>
)}
</View>
</CenteredView>
)
}
export function ProfileModserviceScreenInner({
preferences,
modservice,
}: {
preferences: UsePreferencesQueryResponse
modservice: AppBskyModerationDefs.ModServiceViewDetailed
}) {
const t = useTheme()
const {_} = useLingui()
const {height: minHeight} = useSafeAreaFrame()
const {hasSession} = useSession()
const {track} = useAnalytics()
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
useUnlikeMutation()
const {mutateAsync: toggleEnabled, variables: enabledVariables} =
useModServiceEnableMutation()
const [likeUri, setLikeUri] = React.useState<string>(
modservice.viewer?.like || '',
)
// TODO error state
const [_enablementError, setEnablementError] = React.useState<string>('')
const isLiked = !!likeUri
const groups = React.useMemo(() => {
return getLabelGroupsFromLabels(modservice.policies.labelValues).filter(
def => def.configurable,
)
}, [modservice.policies.labelValues])
const modservicePreferences = React.useMemo(() => {
return preferences.moderationOpts.mods.find(
p => p.did === modservice.creator.did,
)
}, [modservice.creator.did, preferences.moderationOpts.mods])
const isSubscribed = preferences.moderationOpts.mods.find(
mod => mod.did === modservice.creator.did,
)
const isEnabled = Boolean(
enabledVariables?.enabled ??
preferences.moderationOpts.mods.find(
mod => mod.did === modservice.creator.did && mod.enabled,
),
)
useSetTitle(modservice.creator.displayName || modservice.creator.handle)
const onToggleLiked = React.useCallback(async () => {
try {
Haptics.default()
if (isLiked && likeUri) {
await unlikeMod({uri: likeUri})
track('CustomFeed:Unlike')
setLikeUri('')
} else {
const res = await likeMod({uri: modservice.uri, cid: modservice.cid})
track('CustomFeed:Like')
setLikeUri(res.uri)
}
} catch (e: any) {
Toast.show(
_(
msg`There was an an issue contacting the server, please check your internet connection and try again.`,
),
)
logger.error(`Failed to toggle labeler like`, {message: e.message})
}
}, [likeUri, isLiked, modservice, likeMod, unlikeMod, track, _])
const onToggleLabelerEnabled = React.useCallback(async () => {
try {
await toggleEnabled({
did: modservice.creator.did,
enabled: !isEnabled,
})
} catch (e: any) {
setEnablementError(e.message)
logger.error(`Failed to toggle labeler enabled`, {message: e.message})
}
}, [toggleEnabled, isEnabled, modservice.creator.did])
return (
<ScrollView
scrollEventThrottle={1}
contentContainerStyle={{
minHeight,
borderWidth: 0,
paddingHorizontal: a.px_xl.paddingLeft,
}}>
<Header modservice={modservice} preferences={preferences} />
<View style={[a.gap_md, a.pb_xl]}>
{modservice.description ? (
<RichText
testID="modinfoDescription"
resolveFacets
value={modservice.description}
style={[a.text_md, a.leading_normal]}
/>
) : (
<Text
style={[
a.text_md,
a.leading_normal,
t.atoms.text_contrast_medium,
{fontStyle: 'italic'},
]}>
<Trans>
Moderation service managed by @{modservice.creator.handle}
</Trans>
</Text>
)}
<View style={[a.flex_row, a.gap_md, a.align_center]}>
<Button
testID="toggleLikeBtn"
size="small"
color="secondary"
variant="solid"
shape="round"
label={_(msg`Like this feed`)}
disabled={!hasSession || isLikePending || isUnlikePending}
onPress={onToggleLiked}>
{isLiked ? (
<HeartFilled fill={t.palette.negative_400} />
) : (
<Heart fill={t.atoms.text_contrast_medium.color} />
)}
</Button>
{typeof modservice.likeCount === 'number' && (
<InlineLink
to={'#todo'}
style={[t.atoms.text_contrast_medium, a.font_bold]}>
<Trans>
Liked by {modservice.likeCount}{' '}
{pluralize(modservice.likeCount, 'user')}
</Trans>
</InlineLink>
)}
</View>
</View>
<Divider />
{isSubscribed ? (
<View style={[a.flex_row, a.pr_lg, a.pt_xl]}>
<View style={[a.gap_sm, a.flex_1]}>
<Text style={[a.text_md, a.font_bold]}>Settings</Text>
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
Enable or disable labels from this service.
</Text>
</View>
<Toggle.Item
name="enable"
value={isEnabled}
onChange={onToggleLabelerEnabled}
label={
isEnabled
? _(msg`Disable this moderation service`)
: _(msg`Enable this moderation service`)
}>
<Toggle.Label>{isEnabled ? 'Enabled' : 'Disabled'}</Toggle.Label>
<Toggle.Switch />
</Toggle.Item>
</View>
) : (
<View style={[a.gap_sm, a.pt_xl]}>
<Text style={[a.text_md, a.font_bold]}>Labels</Text>
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
This labeler moderates the following types of content.
</Text>
</View>
)}
<View
style={[
a.gap_md,
a.mt_xl,
t.atoms.bg_contrast_25,
a.rounded_md,
a.border,
a.py_md,
t.atoms.border_contrast_low,
]}>
{groups.map((def, i) => {
return (
<React.Fragment key={def.id}>
{i !== 0 && <Divider />}
<View style={[a.px_lg]}>
<PreferenceRow
disabled={isEnabled ? undefined : true}
labelGroup={def.id}
modservicePreferences={modservicePreferences}
/>
</View>
</React.Fragment>
)
})}
</View>
<View style={{height: 100}} />
</ScrollView>
)
}