diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index b78f70112c..7ac62222ec 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -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) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 89d43d9f00..f4960f51b9 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -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`)}} /> - ProfileModserviceScreen} - options={{title: title(msg`Moderation service`)}} - /> Storybook} diff --git a/src/screens/ProfileModerationService/PreferenceRow.tsx b/src/components/ModerationLabelPref/index.tsx similarity index 65% rename from src/screens/ProfileModerationService/PreferenceRow.tsx rename to src/components/ModerationLabelPref/index.tsx index 9cde55db95..0407704889 100644 --- a/src/screens/ProfileModerationService/PreferenceRow.tsx +++ b/src/components/ModerationLabelPref/index.tsx @@ -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, ]}> @@ -72,7 +48,7 @@ export function PreferenceRow({ - {modservicePreferences && ( + {!disabled && ( - 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 ( - - - - - - - - - - - - {isEnabled ? 'Enabled' : 'Disabled'} - - - - - - - - - Configure more settings for this labeler{' '} - - here. - - - - - ) -} - -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 ( - - - - - Configure enabled labelers - - - - - {groupInfoStrings.name} - - {groupInfoStrings.description} - - - - - - Select which labelers to use for this type of content: - - - - {modservices.map(modservice => { - return ( - - ) - })} - - - ) -} diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index 646126fdd5..c13a37098f 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -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({ - // 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) => { - setSettingsDialogProps({ - labelGroup, - modservices, - }) - modSettingsDialogControl.open() - }, - [modSettingsDialogControl], - ) - const {mutateAsync: setAdultContentPref, variables: optimisticAdultContent} = usePreferencesSetAdultContentMutation() const adultContentEnabled = !!( @@ -206,11 +168,6 @@ export function ModerationScreenInner({ return ( - - - - - - g.id === 'porn')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> + - g.id === 'suggestive')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> + - g.id === 'nudity')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> + - g.id === 'violence')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> + )} - - - - - Hide posts by bots - - - Bots are automated users which follow the rules. - - - - - - {false ? Enabled : Disabled} - - - - - - {modservices.map(mod => { return ( - - - - - - + <> + + + + + + + + ) })} - { - undefined /* - Bluesky Safety{' '} - - @safety.bsky.app - - - - g.id === 'intolerance')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> - - g.id === 'misrepresentation')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> - - g.id === 'security')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> - - g.id === 'spam')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> - - - {modservices.map(mod => { - return ( - <> - - {mod.creator.displayName}{' '} - - @{mod.creator.handle} - - - - {mod.policies.labelValues - .filter(label => !label.startsWith('!')) - .map((label, i) => { - return ( - - {i !== 0 && } - - - ) - })} - - - ) - })} - - - Other - - - g.id === 'bot')!.id} - labelers={[]} - preferences={preferences} - openModSettingsDialog={openModSettingsDialog} - /> - - - - Content filtering - - - - {groups.map((def, i) => { - const labelers = labelGroupToLabelerMap[def.id] || [] - return ( - - {i !== 0 && } - - - ) - })} - */ - } - 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 ( - - - - - {name} - - {description} - - - - - - {labelOptions.warn} - - - {labelOptions.hide} - - { - undefined /* - {labelOptions.show} - */ - } - - - - - {!!mods.length && ( - - - - {settingsOpen && ( - - - - {mods.map((mod, i) => { - return ( - - {i !== 0 && } - - - - ) - })} - - )} - - )} - - ) -} - -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 ( - - - - {title}{' '} - - {isLabelerEnabled ? '' : '(labeler disabled)'} - - - - - - ) -} - function PwiOptOut() { const t = useTheme() const {_} = useLingui() diff --git a/src/screens/ProfileModerationService/ErrorState.tsx b/src/screens/Profile/ErrorState.tsx similarity index 100% rename from src/screens/ProfileModerationService/ErrorState.tsx rename to src/screens/Profile/ErrorState.tsx diff --git a/src/screens/Profile/Sections/ContentFilters.tsx b/src/screens/Profile/Sections/ContentFilters.tsx index 68b1574fbc..a1def38a44 100644 --- a/src/screens/Profile/Sections/ContentFilters.tsx +++ b/src/screens/Profile/Sections/ContentFilters.tsx @@ -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( - modservice.viewer?.like || '', - ) - // TODO error state - const [_enablementError, setEnablementError] = React.useState('') - - 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 ( - {false /* TODO wanted? isSubscribed*/ ? ( - - - - Enable or disable labels from this service. - - - - - {isEnabled ? 'Enabled' : 'Disabled'} - - - - ) : ( - - - This service labels the following types of content. - - - )} - + + This service labels the following types of content. + {groups.map((def, i) => { return ( {i !== 0 && } - - - + ) })} diff --git a/src/screens/ProfileModerationService/Header.tsx b/src/screens/ProfileModerationService/Header.tsx deleted file mode 100644 index 5ff4dbe014..0000000000 --- a/src/screens/ProfileModerationService/Header.tsx +++ /dev/null @@ -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() - 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('') - - 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 ( - - {!gtMobile && ( - - - - - - - - - - - - - - )} - - - - 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]} - /> - - - - - - - - Moderation service - - - - {gtMobile && ( - - - - - - )} - - - {subscriptionError && ( - - - - An error occurred while editing your subscription. - - - {subscriptionError || enablementError} - - - - )} - - ) -} - -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 ( - - - - - - ) -} diff --git a/src/screens/ProfileModerationService/index.tsx b/src/screens/ProfileModerationService/index.tsx deleted file mode 100644 index 03db5c9480..0000000000 --- a/src/screens/ProfileModerationService/index.tsx +++ /dev/null @@ -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, -) { - 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 ( - - - {isLoading ? ( - - - - ) : error || !(did && preferences && modservice) ? ( - - ) : ( - - )} - - - ) -} - -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( - modservice.viewer?.like || '', - ) - // TODO error state - const [_enablementError, setEnablementError] = React.useState('') - - 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 ( - -
- - - {modservice.description ? ( - - ) : ( - - - Moderation service managed by @{modservice.creator.handle} - - - )} - - - - - {typeof modservice.likeCount === 'number' && ( - - - Liked by {modservice.likeCount}{' '} - {pluralize(modservice.likeCount, 'user')} - - - )} - - - - - - {isSubscribed ? ( - - - Settings - - Enable or disable labels from this service. - - - - - {isEnabled ? 'Enabled' : 'Disabled'} - - - - ) : ( - - Labels - - This labeler moderates the following types of content. - - - )} - - - {groups.map((def, i) => { - return ( - - {i !== 0 && } - - - - - ) - })} - - - - - ) -}