diff --git a/src/components/Pills.tsx b/src/components/Pills.tsx index 1f3bb2b97..a0aef366b 100644 --- a/src/components/Pills.tsx +++ b/src/components/Pills.tsx @@ -1,6 +1,7 @@ import React from 'react' import {View} from 'react-native' import {BSKY_LABELER_DID, type ModerationCause} from '@atproto/api' +import {Trans} from '@lingui/react/macro' import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' @@ -152,3 +153,30 @@ export function Label({ ) } + +export function FollowsYou({size = 'sm'}: CommonProps) { + const t = useTheme() + + const variantStyles = React.useMemo(() => { + switch (size) { + case 'sm': + case 'lg': + default: + return [ + { + paddingHorizontal: 6, + paddingVertical: 3, + borderRadius: 4, + }, + ] + } + }, [size]) + + return ( + + + Follows You + + + ) +} diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 10369124c..9eb1a7bde 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -20,6 +20,7 @@ import {NON_BREAKING_SPACE} from '#/lib/strings/constants' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' +import {useShowFollowsYouBadge} from '#/state/preferences/show-follows-you-badge' import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' @@ -647,13 +648,15 @@ export function Labels({ const moderation = moderateProfile(profile, moderationOpts) const modui = moderation.ui('profileList') const followedBy = profile.viewer?.followedBy + const showFollowsYouBadge = useShowFollowsYouBadge() - if (!followedBy && !modui.inform && !modui.alert) { + if (!(followedBy && showFollowsYouBadge) && !modui.inform && !modui.alert) { return null } return ( + {followedBy && showFollowsYouBadge && } {modui.alerts.map(alert => ( ))} diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index 1d124e978..967327b5d 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -2,10 +2,12 @@ import {View} from 'react-native' import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {Trans} from '@lingui/react/macro' import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' import {sanitizePronouns} from '#/lib/strings/pronouns' import {type Shadow} from '#/state/cache/types' +import {useShowFollowsYouBadge} from '#/state/preferences/show-follows-you-badge' import {useShowLinkInHandle} from '#/state/preferences/show-link-in-handle.tsx' import {atoms as a, useTheme, web} from '#/alf' import {InlineLinkText} from '#/components/Link.tsx' @@ -24,7 +26,9 @@ export function ProfileHeaderHandle({ const {_} = useLingui() const invalidHandle = isInvalidHandle(profile.handle) const pronouns = profile.pronouns + const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy const isBskySocialHandle = profile.handle.endsWith('.bsky.social') + const showFollowsYouBadge = useShowFollowsYouBadge() const showProfileInHandle = useShowLinkInHandle() const sanitized = sanitizeHandle( profile.handle, @@ -37,6 +41,13 @@ export function ProfileHeaderHandle({ style={[a.flex_row, a.gap_sm, a.align_center, {maxWidth: '100%'}]} pointerEvents={disableTaps ? 'none' : IS_IOS ? 'auto' : 'box-none'}> + {showFollowsYouBadge && profile.viewer?.followedBy && !blockHide ? ( + + + Follows you + + + ) : undefined} + setShowFollowsYouBadge(value)} + style={[a.w_full]}> + + Show "Follows you" badge + + + + setDisablePostsMetrics(value)} style={[a.w_full]}> - Disable posts metrics + Disable post counts metrics diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx index 4324d7148..9d1bd8689 100644 --- a/src/screens/Signup/StepInfo/index.tsx +++ b/src/screens/Signup/StepInfo/index.tsx @@ -33,16 +33,12 @@ import {ScreenTransition} from '#/components/ScreenTransition' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import { - isUnderAge, MIN_ACCESS_AGE, useAgeAssuranceRegionConfigWithFallback, } from '#/ageAssurance/util' import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' -import { - useDeviceGeolocationApi, - useIsDeviceGeolocationGranted, -} from '#/geolocation' +import {useDeviceGeolocationApi} from '#/geolocation' import {BackNextButtons} from '../BackNextButtons' function sanitizeDate(date: Date): Date { diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 7aee18bfb..b4a1e0c47 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -161,6 +161,7 @@ const schema = z.object({ disableFollowingMetrics: z.boolean().optional(), disableFollowedByMetrics: z.boolean().optional(), disablePostsMetrics: z.boolean().optional(), + showFollowsYouBadge: z.boolean().optional(), hideSimilarAccountsRecomm: z.boolean().optional(), discoverContextEnabled: z.boolean().optional(), enableSquareAvatars: z.boolean().optional(), @@ -278,6 +279,7 @@ export const defaults: Schema = { disableFollowingMetrics: false, disableFollowedByMetrics: false, disablePostsMetrics: false, + showFollowsYouBadge: false, hideSimilarAccountsRecomm: true, discoverContextEnabled: false, enableSquareAvatars: true, diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index 54e64fb77..3a4e3e8b1 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -41,6 +41,7 @@ import {Provider as OpenRouterProvider} from './openrouter' import {Provider as PdsLabelProvider} from './pds-label' import {Provider as PostNameReplacementProvider} from './post-name-replacement.tsx' import {Provider as RepostCarouselProvider} from './repost-carousel-enabled' +import {Provider as ShowFollowsYouBadgeProvider} from './show-follows-you-badge' import {Provider as ShowLinkInHandleProvider} from './show-link-in-handle' import {Provider as SubtitlesProvider} from './subtitles' import {Provider as TranslationServicePreferenceProvider} from './translation-service-preference' @@ -127,29 +128,31 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - - - - - - - - - - - { - children - } - - - - - - - - - - + + + + + + + + + + + + { + children + } + + + + + + + + + + + diff --git a/src/state/preferences/show-follows-you-badge.tsx b/src/state/preferences/show-follows-you-badge.tsx new file mode 100644 index 000000000..e1cea6c7c --- /dev/null +++ b/src/state/preferences/show-follows-you-badge.tsx @@ -0,0 +1,50 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = persisted.Schema['showFollowsYouBadge'] +type SetContext = (v: persisted.Schema['showFollowsYouBadge']) => void + +const stateContext = React.createContext( + persisted.defaults.showFollowsYouBadge, +) +const setContext = React.createContext( + (_: persisted.Schema['showFollowsYouBadge']) => {}, +) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState(persisted.get('showFollowsYouBadge')) + + const setStateWrapped = React.useCallback( + (showFollowsYouBadge: persisted.Schema['showFollowsYouBadge']) => { + setState(showFollowsYouBadge) + persisted.write('showFollowsYouBadge', showFollowsYouBadge) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate( + 'showFollowsYouBadge', + nextShowFollowsYouBadge => { + setState(nextShowFollowsYouBadge) + }, + ) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export function useShowFollowsYouBadge() { + return React.useContext(stateContext) +} + +export function useSetShowFollowsYouBadge() { + return React.useContext(setContext) +} diff --git a/src/view/com/posts/PostFeedItemCarousel.tsx b/src/view/com/posts/PostFeedItemCarousel.tsx index a6ba100ba..0cf2cd0a7 100644 --- a/src/view/com/posts/PostFeedItemCarousel.tsx +++ b/src/view/com/posts/PostFeedItemCarousel.tsx @@ -1,7 +1,8 @@ import React from 'react' import {Dimensions, ScrollView, View} from 'react-native' -import {msg, Plural} from '@lingui/macro' +import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {Plural} from '@lingui/react/macro' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' import {type FeedPostSlice} from '#/state/queries/post-feed' diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index d2ed42bf3..d03d0ac96 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -1,5 +1,5 @@ import {useCallback} from 'react' -import {msg} from '@lingui/macro' +import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native'