3p moderation services [WIP] (#2550)
* Add modservice screen and profile-header-card * Drop the guidelines for now * Remove ununsed constants * Add label & label group descriptions * Not found state * Reorg, add icon * Subheader * Header * Complete header * Clean up * Add all groups * Fix scroll view * Dialogs side quest * Remove log * Add (WIP) debug mod page * Dialog solution * Add note * Clean up and reorganize localized moderation strings * Memoize * Add example * Add first ReportDialog screen * Report dialog step 2 * Submit * Integrate updates * Move moderation screen * Migrate buttons * Migrate everything * Rough sketch * Fix types * Update atoms values * Abstract ModerationServiceCard * Hook up data to settings page * Handle subscription * Rough enablement * Rough enablement * Some validation, fixes * More work on the mod debug screen * Hook up data * Update invalidation * Hook up data to ReportDialog * Fix native error * Refactor/rewrite the entire moderation-application system * Fix toggles * Add copyright and other option to report * Handle reports on profile vs content * Little cleanup * Get post hiding back in gear * Better loading flow on Mod screen * Clean up Mod screen * Clean up ProfileMod screen * Handle muting correctly * Update enablement on ProfileMod screen * Improve Moderation screen and dialog * Styling, handle disabled labelers * Rework list of labels on own content * Use moderateNotification() * ReportDialog updates * Fix button overflow * Simplify the ProfileModerationService ui * Mod screen design * Move moderation card from the profile header to a tab * Small tweaks to the moderation screen * Enable toggle on mod page * Add notifs to debugmod and dont filter notifs from followed users * Add moderator-service profile view * Wire up more of the modservice data to profiles * A bunch of speculative non-working UI * Cleanup: delete old code * Update ModerationDetailsDialog * Update ReportDialog * Update LabelsOnMe dialog * Handle ReportDialog load better * Rename LabelsOnMeDialog, fix close * Experiment to put labeling under a tab of a normal profile * Moderator variation of profile * Remove dead code and start moving toward latest modsdk * Remove a bunch of now-dead label strings * Update ModDebug to be a bit more intuitive and support custom labels * Minor ui tweaks * Improve consistency of display name blurring * Fix profile-card warning rendering * More debugmod UI tuning * Update to use new labeler semantics * Delete some dead code and do some refactoring * Update profile to pull from labeler definition * Implement new label config controls (wip) * Tweak ui * Implement preference controls on labelers * Rework label pref ui * Get moderation screen working * Add asyncstorage query persistence * Implement label handling * Small cleanup * Implement Likes dialog * Fix: remove text outside of text element * Cleanup * Fix likes dialog on mobile * Implement the label appeal flow * Get report flow working again with temporarily fixed report options * Update onboarding * Enforce limit of ten labeler subscriptions * Fix type errors * Fix lint errors * Improve types of RQ * Some work on Likes dialog, needs discussion * Bit of ReportDialog cleanup * Replace non-single-path SVG * Update nudity descriptions * Update to use new sdk updates * Add adult-content-enabled behavior to label config * Use the default setting of custom labels * Handle global moderation label prefs with the global settings * Fix missing postAuthor * Fix empty moderation page * Add mutewords control back to Mod screen * Tweak adult setting styles * Remove deprecated global labels * Handle underage users on mod screen * Adjust font sizes * Swap in RichText * Like button improvements * Tweaks to Labeler profile * Design tweaks for mod pref dialog * Add tertiary button color * Switch moderation UIs to tertiary color * Update mutewords and hiddenposts to use the new sdk * Add test-environment mod authority * Switch 'gore' to 'graphic-media' * Move nudity out of the adult content control * Remove focus styles from buttons - let the browser behavior handle it * Fixes to the adult content age-gating in moderaiton * Ditch tertiary button color, lighten secondary button * Fix some colors * Remove focused overrides from toggles * Liked by screen * Rework the moderationlabelpref * Fix optimistic like * Cleanup * Change how onboarding handles adult content enabled/disabled * Add special handling of the mod authorities * Tweaks * Update the default labeler avatar to a shield * Add route to go server * Avoid dups due to bad config * Fix attrs * Fix: dont try to detect link/label mismatches on post meta * Correctly show the label behavior when adult content is disabled * Readd the local hiddenPosts handling * WIP * Fix bad merge * Conten hider design tweaks * Fix text string breakage * Adjust source text in ContentHider * Fix link bug * Design tweaks to ContentHider and ModDetailsDialog * Adjust spacing of inform badges * Adjust spacing of embeds in posts * Style tweaks to post/profile alerts * Labels on me and dialog * Remove bad focus styles from post dropdown * Better spacing solution * Tune moderation UIs * Moderation UI tweaks for mobile * Move labelers query on Mod screen * Update to use new SDK appLabelers semantics * Implement report submission * Replace the report modal entirely with the report dialog * Add @ to mod details dialog handle * Bump SDK package * Remove silly type * Add to AWS build CI * Fix ToggleButton overflow * Clean up ModServiceCard, rename to LabelingServiceCard * Hackfix to translate gore labels to graphic-media * Tune content hider sizing on web desktop * Handle self labels * Fix spacing below text-only posts * Fix: send appeals to the right labeler * Give mod page links interactive states * Fix references * Remove focus handling * Remove remnant * Remove the like count from the subscribed labeler listing * Bump @atproto/api@0.11.1 * Remove extra @ * Fix: persist labels to local storage to reduce coverage gaps * update dipendencies * revert dipendencies * Add some explainers on how blocking affects labelers * Tweak copy * Fix underline color in header * Fix profile menu * Handle card overflow * Remove metrics from header * Mute 'account' not 'user' * Show metrics if self * Show the labels tab on logged out view * Fix bad merge * Use purple theming on labelers * Tighten space on LabelerCard * Set staleTime to 6hrs for labeler details * Memoize the memoizers * Drop staleTime to 60s * Move label defs into a context to reduce recomputes * Submit view tweaks * Move labeler fetch below auth * Mitigation: hardcode the bluesky moderation labeler name * Bump sdk * Add missing translated string Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Add missing translated string Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Hailey's fix for incorrect profile tabs Co-authored-by: Hailey <me@haileyok.com> * Feedback * Fix borders, add bottom space * Hailey's fix pt 2 Co-authored-by: Hailey <me@haileyok.com> * Fix post tabs * Integrate feedback pt 1 Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Integrate feedback pt 2 Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Integrate feedback pt 3 Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Integrate feedback pt 4 Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Integrate feedback pt 5 Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Integrate feedback pt 6 Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Integrate feedback pt 7 Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Integrate feedback pt 8 Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> * Format * Integrate new bday modal * Use public agent for getServices * Update casing --------- Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: Takayuki KUSANO <65759+tkusano@users.noreply.github.com> Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
parent
d5ebbeb3fc
commit
20d463ff2f
165 changed files with 7034 additions and 5009 deletions
560
src/screens/Moderation/index.tsx
Normal file
560
src/screens/Moderation/index.tsx
Normal file
|
@ -0,0 +1,560 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {ComAtprotoLabelDefs} from '@atproto/api'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {LABELS} from '@atproto/api'
|
||||
import {useSafeAreaFrame} from 'react-native-safe-area-context'
|
||||
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useSession} from '#/state/session'
|
||||
import {
|
||||
useProfileQuery,
|
||||
useProfileUpdateMutation,
|
||||
} from '#/state/queries/profile'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
|
||||
import {
|
||||
UsePreferencesQueryResponse,
|
||||
useMyLabelersQuery,
|
||||
usePreferencesQuery,
|
||||
usePreferencesSetAdultContentMutation,
|
||||
} from '#/state/queries/preferences'
|
||||
|
||||
import {getLabelingServiceTitle} from '#/lib/moderation'
|
||||
import {logger} from '#/logger'
|
||||
import {useTheme, atoms as a, useBreakpoints, ViewStyleProp} 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 {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
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 {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
|
||||
function ErrorState({error}: {error: string}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View style={[a.p_xl]}>
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.leading_normal,
|
||||
a.pb_md,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>
|
||||
Hmmmm, it seems we're having trouble loading this data. See below for
|
||||
more details. If this issue persists, please contact us.
|
||||
</Trans>
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
a.relative,
|
||||
a.py_md,
|
||||
a.px_lg,
|
||||
a.rounded_md,
|
||||
a.mb_2xl,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Text style={[a.text_md, a.leading_normal]}>{error}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function ModerationScreen(
|
||||
_props: NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>,
|
||||
) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {
|
||||
isLoading: isPreferencesLoading,
|
||||
error: preferencesError,
|
||||
data: preferences,
|
||||
} = usePreferencesQuery()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {height} = useSafeAreaFrame()
|
||||
|
||||
const isLoading = isPreferencesLoading
|
||||
const error = preferencesError
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
testID="moderationScreen"
|
||||
style={[
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg,
|
||||
{minHeight: height},
|
||||
...(gtMobile ? [a.border_l, a.border_r] : []),
|
||||
]}>
|
||||
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
|
||||
|
||||
{isLoading ? (
|
||||
<View style={[a.w_full, a.align_center, a.pt_2xl]}>
|
||||
<Loader size="xl" fill={t.atoms.text.color} />
|
||||
</View>
|
||||
) : error || !preferences ? (
|
||||
<ErrorState
|
||||
error={
|
||||
preferencesError?.toString() ||
|
||||
_(msg`Something went wrong, please try again.`)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ModerationScreenInner preferences={preferences} />
|
||||
)}
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
||||
|
||||
function SubItem({
|
||||
title,
|
||||
icon: Icon,
|
||||
style,
|
||||
}: ViewStyleProp & {
|
||||
title: string
|
||||
icon: React.ComponentType<SVGIconProps>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.p_lg,
|
||||
a.gap_sm,
|
||||
style,
|
||||
]}>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
||||
<Icon size="md" style={[t.atoms.text_contrast_medium]} />
|
||||
<Text style={[a.text_sm, a.font_bold]}>{title}</Text>
|
||||
</View>
|
||||
<ChevronRight
|
||||
size="sm"
|
||||
style={[t.atoms.text_contrast_low, a.self_end, {paddingBottom: 2}]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function ModerationScreenInner({
|
||||
preferences,
|
||||
}: {
|
||||
preferences: UsePreferencesQueryResponse
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {screen} = useAnalytics()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {mutedWordsDialogControl} = useGlobalDialogsControlContext()
|
||||
const birthdateDialogControl = Dialog.useDialogControl()
|
||||
const {
|
||||
isLoading: isLabelersLoading,
|
||||
data: labelers,
|
||||
error: labelersError,
|
||||
} = useMyLabelersQuery()
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
screen('Moderation')
|
||||
setMinimalShellMode(false)
|
||||
}, [screen, setMinimalShellMode]),
|
||||
)
|
||||
|
||||
const {mutateAsync: setAdultContentPref, variables: optimisticAdultContent} =
|
||||
usePreferencesSetAdultContentMutation()
|
||||
const adultContentEnabled = !!(
|
||||
(optimisticAdultContent && optimisticAdultContent.enabled) ||
|
||||
(!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled)
|
||||
)
|
||||
const ageNotSet = !preferences.userAge
|
||||
const isUnderage = (preferences.userAge || 0) < 18
|
||||
|
||||
const onToggleAdultContentEnabled = React.useCallback(
|
||||
async (selected: boolean) => {
|
||||
try {
|
||||
await setAdultContentPref({
|
||||
enabled: selected,
|
||||
})
|
||||
} catch (e: any) {
|
||||
logger.error(`Failed to set adult content pref`, {
|
||||
message: e.message,
|
||||
})
|
||||
}
|
||||
},
|
||||
[setAdultContentPref],
|
||||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
a.border_0,
|
||||
a.pt_2xl,
|
||||
a.px_lg,
|
||||
gtMobile && a.px_2xl,
|
||||
]}>
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}>
|
||||
<Trans>Moderation tools</Trans>
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Button
|
||||
testID="mutedWordsBtn"
|
||||
label={_(msg`Open muted words and tags settings`)}
|
||||
onPress={() => mutedWordsDialogControl.open()}>
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Muted words & tags`)}
|
||||
icon={Filter}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
<Divider />
|
||||
<Link testID="moderationlistsBtn" to="/moderation/modlists">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Moderation lists`)}
|
||||
icon={Group}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
<Divider />
|
||||
<Link testID="mutedAccountsBtn" to="/moderation/muted-accounts">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Muted accounts`)}
|
||||
icon={Person}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
<Divider />
|
||||
<Link testID="blockedAccountsBtn" to="/moderation/blocked-accounts">
|
||||
{state => (
|
||||
<SubItem
|
||||
title={_(msg`Blocked accounts`)}
|
||||
icon={CircleBanSign}
|
||||
style={[
|
||||
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Content filters</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={[a.gap_md]}>
|
||||
{ageNotSet && (
|
||||
<>
|
||||
<Button
|
||||
label={_(msg`Confirm your birthdate`)}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
onPress={() => {
|
||||
birthdateDialogControl.open()
|
||||
}}
|
||||
style={[a.justify_between, a.rounded_md, a.px_lg, a.py_lg]}>
|
||||
<ButtonText>
|
||||
<Trans>Confirm your age:</Trans>
|
||||
</ButtonText>
|
||||
<ButtonText>
|
||||
<Trans>Set birthdate</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
|
||||
<BirthDateSettingsDialog
|
||||
control={birthdateDialogControl}
|
||||
preferences={preferences}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
{!ageNotSet && !isUnderage && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.py_lg,
|
||||
a.px_lg,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
]}>
|
||||
<Text style={[a.font_semibold, t.atoms.text_contrast_high]}>
|
||||
<Trans>Enable adult content</Trans>
|
||||
</Text>
|
||||
<Toggle.Item
|
||||
label={_(msg`Toggle to enable or disable adult content`)}
|
||||
name="adultContent"
|
||||
value={adultContentEnabled}
|
||||
onChange={onToggleAdultContentEnabled}>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
{adultContentEnabled ? (
|
||||
<Trans>Enabled</Trans>
|
||||
) : (
|
||||
<Trans>Disabled</Trans>
|
||||
)}
|
||||
</Text>
|
||||
<Toggle.Switch />
|
||||
</View>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
{!isUnderage && adultContentEnabled && (
|
||||
<>
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.porn} />
|
||||
<Divider />
|
||||
<GlobalModerationLabelPref
|
||||
labelValueDefinition={LABELS.sexual}
|
||||
/>
|
||||
<Divider />
|
||||
<GlobalModerationLabelPref
|
||||
labelValueDefinition={LABELS['graphic-media']}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<GlobalModerationLabelPref labelValueDefinition={LABELS.nudity} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Advanced</Trans>
|
||||
</Text>
|
||||
|
||||
{isLabelersLoading ? (
|
||||
<Loader />
|
||||
) : labelersError || !labelers ? (
|
||||
<View style={[a.p_lg, a.rounded_sm, t.atoms.bg_contrast_25]}>
|
||||
<Text>
|
||||
<Trans>
|
||||
We were unable to load your configured labelers at this time.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[a.rounded_sm, t.atoms.bg_contrast_25]}>
|
||||
{labelers.map((labeler, i) => {
|
||||
return (
|
||||
<React.Fragment key={labeler.creator.did}>
|
||||
{i !== 0 && <Divider />}
|
||||
<LabelingService.Link labeler={labeler}>
|
||||
{state => (
|
||||
<LabelingService.Outer
|
||||
style={[
|
||||
i === 0 && {
|
||||
borderTopLeftRadius: a.rounded_sm.borderRadius,
|
||||
borderTopRightRadius: a.rounded_sm.borderRadius,
|
||||
},
|
||||
i === labelers.length - 1 && {
|
||||
borderBottomLeftRadius: a.rounded_sm.borderRadius,
|
||||
borderBottomRightRadius: a.rounded_sm.borderRadius,
|
||||
},
|
||||
(state.hovered || state.pressed) && [
|
||||
t.atoms.bg_contrast_50,
|
||||
],
|
||||
]}>
|
||||
<LabelingService.Avatar />
|
||||
<LabelingService.Content>
|
||||
<LabelingService.Title
|
||||
value={getLabelingServiceTitle({
|
||||
displayName: labeler.creator.displayName,
|
||||
handle: labeler.creator.handle,
|
||||
})}
|
||||
/>
|
||||
<LabelingService.Description
|
||||
value={labeler.creator.description}
|
||||
handle={labeler.creator.handle}
|
||||
/>
|
||||
</LabelingService.Content>
|
||||
</LabelingService.Outer>
|
||||
)}
|
||||
</LabelingService.Link>
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Text
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
a.pt_2xl,
|
||||
a.pb_md,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Logged-out visibility</Trans>
|
||||
</Text>
|
||||
|
||||
<PwiOptOut />
|
||||
|
||||
<View style={{height: 200}} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function PwiOptOut() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||
const updateProfile = useProfileUpdateMutation()
|
||||
|
||||
const isOptedOut =
|
||||
profile?.labels?.some(l => l.val === '!no-unauthenticated') || false
|
||||
const canToggle = profile && !updateProfile.isPending
|
||||
|
||||
const onToggleOptOut = React.useCallback(() => {
|
||||
if (!profile) {
|
||||
return
|
||||
}
|
||||
let wasAdded = false
|
||||
updateProfile.mutate({
|
||||
profile,
|
||||
updates: existing => {
|
||||
// create labels attr if needed
|
||||
existing.labels = ComAtprotoLabelDefs.isSelfLabels(existing.labels)
|
||||
? existing.labels
|
||||
: {
|
||||
$type: 'com.atproto.label.defs#selfLabels',
|
||||
values: [],
|
||||
}
|
||||
|
||||
// toggle the label
|
||||
const hasLabel = existing.labels.values.some(
|
||||
l => l.val === '!no-unauthenticated',
|
||||
)
|
||||
if (hasLabel) {
|
||||
wasAdded = false
|
||||
existing.labels.values = existing.labels.values.filter(
|
||||
l => l.val !== '!no-unauthenticated',
|
||||
)
|
||||
} else {
|
||||
wasAdded = true
|
||||
existing.labels.values.push({val: '!no-unauthenticated'})
|
||||
}
|
||||
|
||||
// delete if no longer needed
|
||||
if (existing.labels.values.length === 0) {
|
||||
delete existing.labels
|
||||
}
|
||||
return existing
|
||||
},
|
||||
checkCommitted: res => {
|
||||
const exists = !!res.data.labels?.some(
|
||||
l => l.val === '!no-unauthenticated',
|
||||
)
|
||||
return exists === wasAdded
|
||||
},
|
||||
})
|
||||
}, [updateProfile, profile])
|
||||
|
||||
return (
|
||||
<View style={[a.pt_sm]}>
|
||||
<View style={[a.flex_row, a.align_center, a.justify_between, a.gap_lg]}>
|
||||
<Toggle.Item
|
||||
disabled={!canToggle}
|
||||
value={isOptedOut}
|
||||
onChange={onToggleOptOut}
|
||||
name="logged_out_visibility"
|
||||
label={_(
|
||||
msg`Discourage apps from showing my account to logged-out users`,
|
||||
)}>
|
||||
<Toggle.Switch />
|
||||
<Toggle.Label style={[a.text_md]}>
|
||||
<Trans>
|
||||
Discourage apps from showing my account to logged-out users
|
||||
</Trans>
|
||||
</Toggle.Label>
|
||||
</Toggle.Item>
|
||||
|
||||
{updateProfile.isPending && <Loader />}
|
||||
</View>
|
||||
|
||||
<View style={[a.pt_md, a.gap_md, {paddingLeft: 38}]}>
|
||||
<Text style={[a.leading_snug, t.atoms.text_contrast_high]}>
|
||||
<Trans>
|
||||
Bluesky will not show your profile and posts to logged-out users.
|
||||
Other apps may not honor this request. This does not make your
|
||||
account private.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text style={[a.font_bold, a.leading_snug, t.atoms.text_contrast_high]}>
|
||||
<Trans>
|
||||
Note: Bluesky is an open and public network. This setting only
|
||||
limits the visibility of your content on the Bluesky app and
|
||||
website, and other apps may not respect this setting. Your content
|
||||
may still be shown to logged-out users by other apps and websites.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<InlineLink to="https://blueskyweb.zendesk.com/hc/en-us/articles/15835264007693-Data-Privacy">
|
||||
<Trans>Learn more about what is public on Bluesky.</Trans>
|
||||
</InlineLink>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue