From df20ae237eaf434c6ed0fd032f8328cd9b8c352c Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 26 Aug 2025 09:54:19 -0500 Subject: Threads v2 cleanup (#8902) * Delete root PostThread component * Remove PostThreadItem, migrate DebugMod to use new components * Remove other unused components * Move PostThreadFollowBtn to new home * Move PostThreadComposePrompt to new home * Remove gate * Keep naming in DebugMod * rm v1 prefs --------- Co-authored-by: Samuel Newman --- .../components/ThreadItemAnchorFollowButton.tsx | 139 +++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx (limited to 'src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx') diff --git a/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx b/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx new file mode 100644 index 000000000..d4cf120cf --- /dev/null +++ b/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx @@ -0,0 +1,139 @@ +import React from 'react' +import {type AppBskyActorDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {logger} from '#/logger' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import { + useProfileFollowMutationQueue, + useProfileQuery, +} from '#/state/queries/profile' +import {useRequireAuth} from '#/state/session' +import * as Toast from '#/view/com/util/Toast' +import {atoms as a, useBreakpoints} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' +import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' + +export function ThreadItemAnchorFollowButton({did}: {did: string}) { + const {data: profile, isLoading} = useProfileQuery({did}) + + // We will never hit this - the profile will always be cached or loaded above + // but it keeps the typechecker happy + if (isLoading || !profile) return null + + return +} + +function PostThreadFollowBtnLoaded({ + profile: profileUnshadowed, +}: { + profile: AppBskyActorDefs.ProfileViewDetailed +}) { + const navigation = useNavigation() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + const profile = useProfileShadow(profileUnshadowed) + const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( + profile, + 'PostThreadItem', + ) + const requireAuth = useRequireAuth() + + const isFollowing = !!profile.viewer?.following + const isFollowedBy = !!profile.viewer?.followedBy + const [wasFollowing, setWasFollowing] = React.useState(isFollowing) + + // This prevents the button from disappearing as soon as we follow. + const showFollowBtn = React.useMemo( + () => !isFollowing || !wasFollowing, + [isFollowing, wasFollowing], + ) + + /** + * We want this button to stay visible even after following, so that the user can unfollow if they want. + * However, we need it to disappear after we push to a screen and then come back. We also need it to + * show up if we view the post while following, go to the profile and unfollow, then come back to the + * post. + * + * We want to update wasFollowing both on blur and on focus so that we hit all these cases. On native, + * we could do this only on focus because the transition animation gives us time to not notice the + * sudden rendering of the button. However, on web if we do this, there's an obvious flicker once the + * button renders. So, we update the state in both cases. + */ + React.useEffect(() => { + const updateWasFollowing = () => { + if (wasFollowing !== isFollowing) { + setWasFollowing(isFollowing) + } + } + + const unsubscribeFocus = navigation.addListener('focus', updateWasFollowing) + const unsubscribeBlur = navigation.addListener('blur', updateWasFollowing) + + return () => { + unsubscribeFocus() + unsubscribeBlur() + } + }, [isFollowing, wasFollowing, navigation]) + + const onPress = React.useCallback(() => { + if (!isFollowing) { + requireAuth(async () => { + try { + await queueFollow() + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to follow', {message: String(e)}) + Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') + } + } + }) + } else { + requireAuth(async () => { + try { + await queueUnfollow() + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to unfollow', {message: String(e)}) + Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') + } + } + }) + } + }, [isFollowing, requireAuth, queueFollow, _, queueUnfollow]) + + if (!showFollowBtn) return null + + return ( + + ) +} -- cgit 1.4.1