From 5db56277c05c6c8a6357f0e463d893baabd80b03 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 31 Jan 2024 14:14:37 -0800 Subject: [PATCH] Onboarding moderation improvements (#2713) * create separate label group arrays * render adult and other label groups separately * animate in/out the additional settings * improve toggle logic * support animations on all platforms * remove debug * update notice, prevent running animations on mount * reorg imports --- .../AdultContentEnabledPref.tsx | 125 ++++++++---------- .../StepModeration/ModerationOption.tsx | 49 ++++--- .../Onboarding/StepModeration/index.tsx | 47 ++++++- src/state/queries/preferences/types.ts | 10 +- 4 files changed, 130 insertions(+), 101 deletions(-) diff --git a/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx b/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx index bc4c0387..6b456de8 100644 --- a/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx +++ b/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx @@ -2,19 +2,17 @@ import React from 'react' import {View} from 'react-native' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' +import {UseMutateFunction} from '@tanstack/react-query' -import {isIOS} from '#/platform/detection' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' -import { - usePreferencesQuery, - usePreferencesSetAdultContentMutation, -} from '#/state/queries/preferences' +import {usePreferencesQuery} from '#/state/queries/preferences' import {logger} from '#/logger' import {Text} from '#/components/Typography' -import {InlineLink} from '#/components/Link' import * as Toggle from '#/components/forms/Toggle' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import * as Prompt from '#/components/Prompt' +import {isIOS} from '#/platform/detection' function Card({children}: React.PropsWithChildren<{}>) { const t = useTheme() @@ -36,16 +34,25 @@ function Card({children}: React.PropsWithChildren<{}>) { ) } -export function AdultContentEnabledPref() { +export function AdultContentEnabledPref({ + mutate, + variables, +}: { + mutate: UseMutateFunction + variables: {enabled: boolean} | undefined +}) { const {_} = useLingui() const t = useTheme() + const prompt = Prompt.usePromptControl() // Reuse logic here form ContentFilteringSettings.tsx const {data: preferences} = usePreferencesQuery() - const {mutate, variables} = usePreferencesSetAdultContentMutation() const onToggleAdultContent = React.useCallback(async () => { - if (isIOS) return + if (isIOS) { + prompt.open() + return + } try { mutate({ @@ -57,15 +64,33 @@ export function AdultContentEnabledPref() { ) logger.error('Failed to update preferences with server', {error: e}) } - }, [variables, preferences, mutate, _]) + }, [variables, preferences, mutate, _, prompt]) if (!preferences) return null - if (isIOS) { - if (preferences?.adultContentEnabled === true) { - return null - } else { - return ( + return ( + <> + {preferences.userAge && preferences.userAge >= 18 ? ( + + + + Enable Adult Content + + + + + ) : ( - - Adult content can only be enabled via the Web at{' '} - - bsky.app - - . - + You must be 18 years or older to enable adult content - ) - } - } else { - if (preferences?.userAge) { - if (preferences.userAge >= 18) { - return ( - - - - Enable Adult Content - - - - - ) - } else { - return ( - - - - - You must be 18 years or older to enable adult content - - - - ) - } - } + )} - return null - } + + Adult Content + + + Due to Apple policies, adult content can only be enabled on the web + after completing sign up. + + + + OK + + + + ) } diff --git a/src/screens/Onboarding/StepModeration/ModerationOption.tsx b/src/screens/Onboarding/StepModeration/ModerationOption.tsx index 904c4729..d216692d 100644 --- a/src/screens/Onboarding/StepModeration/ModerationOption.tsx +++ b/src/screens/Onboarding/StepModeration/ModerationOption.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native' import {LabelPreference} from '@atproto/api' import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import Animated, {Easing, Layout, FadeIn} from 'react-native-reanimated' import { CONFIGURABLE_LABEL_GROUPS, @@ -16,8 +17,10 @@ import * as ToggleButton from '#/components/forms/ToggleButton' export function ModerationOption({ labelGroup, + isMounted, }: { labelGroup: ConfigurableLabelGroup + isMounted: React.MutableRefObject }) { const {_} = useLingui() const t = useTheme() @@ -41,7 +44,7 @@ export function ModerationOption({ } return ( - + ]} + layout={Layout.easing(Easing.ease).duration(200)} + entering={isMounted.current ? FadeIn : undefined}> {groupInfo.title} @@ -57,29 +62,23 @@ export function ModerationOption({ - {!preferences?.adultContentEnabled && groupInfo.isAdultImagery ? ( - - {labels.hide} - - ) : ( - - - {labels.hide} - - - {labels.warn} - - - {labels.show} - - - )} + + + {labels.hide} + + + {labels.warn} + + + {labels.show} + + - + ) } diff --git a/src/screens/Onboarding/StepModeration/index.tsx b/src/screens/Onboarding/StepModeration/index.tsx index 18ac037c..c831b688 100644 --- a/src/screens/Onboarding/StepModeration/index.tsx +++ b/src/screens/Onboarding/StepModeration/index.tsx @@ -2,9 +2,14 @@ import React from 'react' import {View} from 'react-native' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' +import Animated, {Easing, Layout} from 'react-native-reanimated' import {atoms as a} from '#/alf' -import {configurableLabelGroups} from 'state/queries/preferences' +import { + configurableAdultLabelGroups, + configurableOtherLabelGroups, + usePreferencesSetAdultContentMutation, +} from 'state/queries/preferences' import {Divider} from '#/components/Divider' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' @@ -23,11 +28,32 @@ import {AdultContentEnabledPref} from '#/screens/Onboarding/StepModeration/Adult import {Context} from '#/screens/Onboarding/state' import {IconCircle} from '#/screens/Onboarding/IconCircle' +function AnimatedDivider() { + return ( + + + + ) +} + export function StepModeration() { const {_} = useLingui() const {track} = useAnalytics() const {state, dispatch} = React.useContext(Context) const {data: preferences} = usePreferencesQuery() + const {mutate, variables} = usePreferencesSetAdultContentMutation() + + // We need to know if the screen is mounted so we know if we want to run entering animations + // https://github.com/software-mansion/react-native-reanimated/discussions/2513 + const isMounted = React.useRef(false) + React.useLayoutEffect(() => { + isMounted.current = true + }, []) + + const adultContentEnabled = !!( + (variables && variables.enabled) || + (!variables && preferences?.adultContentEnabled) + ) const onContinue = React.useCallback(() => { dispatch({type: 'next'}) @@ -57,14 +83,23 @@ export function StepModeration() { ) : ( <> - + - {configurableLabelGroups.map((g, index) => ( + {adultContentEnabled && + configurableAdultLabelGroups.map((g, index) => ( + + {index === 0 && } + + + + ))} + + {configurableOtherLabelGroups.map((g, index) => ( - {index === 0 && } - - + {!adultContentEnabled && index === 0 && } + + ))} diff --git a/src/state/queries/preferences/types.ts b/src/state/queries/preferences/types.ts index cd9a2e8f..45c9eed7 100644 --- a/src/state/queries/preferences/types.ts +++ b/src/state/queries/preferences/types.ts @@ -5,15 +5,23 @@ import { BskyFeedViewPreference, } from '@atproto/api' -export const configurableLabelGroups = [ +export const configurableAdultLabelGroups = [ 'nsfw', 'nudity', 'suggestive', 'gore', +] as const + +export const configurableOtherLabelGroups = [ 'hate', 'spam', 'impersonation', ] as const + +export const configurableLabelGroups = [ + ...configurableAdultLabelGroups, + ...configurableOtherLabelGroups, +] as const export type ConfigurableLabelGroup = (typeof configurableLabelGroups)[number] export type LabelGroup =