mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {AppBskyActorDefs} from '@atproto/api'
3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5import {useNavigation} from '@react-navigation/native'
6
7import {logger} from '#/logger'
8import {Shadow, useProfileShadow} from '#/state/cache/profile-shadow'
9import {
10 useProfileFollowMutationQueue,
11 useProfileQuery,
12} from '#/state/queries/profile'
13import {useRequireAuth} from '#/state/session'
14import * as Toast from '#/view/com/util/Toast'
15import {atoms as a, useBreakpoints} from '#/alf'
16import {Button, ButtonIcon, ButtonText} from '#/components/Button'
17import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
18import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
19
20export function PostThreadFollowBtn({did}: {did: string}) {
21 const {data: profile, isLoading} = useProfileQuery({did})
22
23 // We will never hit this - the profile will always be cached or loaded above
24 // but it keeps the typechecker happy
25 if (isLoading || !profile) return null
26
27 return <PostThreadFollowBtnLoaded profile={profile} />
28}
29
30function PostThreadFollowBtnLoaded({
31 profile: profileUnshadowed,
32}: {
33 profile: AppBskyActorDefs.ProfileViewDetailed
34}) {
35 const navigation = useNavigation()
36 const {_} = useLingui()
37 const {gtMobile} = useBreakpoints()
38 const profile: Shadow<AppBskyActorDefs.ProfileViewBasic> =
39 useProfileShadow(profileUnshadowed)
40 const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
41 profile,
42 'PostThreadItem',
43 )
44 const requireAuth = useRequireAuth()
45
46 const isFollowing = !!profile.viewer?.following
47 const isFollowedBy = !!profile.viewer?.followedBy
48 const [wasFollowing, setWasFollowing] = React.useState<boolean>(isFollowing)
49
50 // This prevents the button from disappearing as soon as we follow.
51 const showFollowBtn = React.useMemo(
52 () => !isFollowing || !wasFollowing,
53 [isFollowing, wasFollowing],
54 )
55
56 /**
57 * We want this button to stay visible even after following, so that the user can unfollow if they want.
58 * However, we need it to disappear after we push to a screen and then come back. We also need it to
59 * show up if we view the post while following, go to the profile and unfollow, then come back to the
60 * post.
61 *
62 * We want to update wasFollowing both on blur and on focus so that we hit all these cases. On native,
63 * we could do this only on focus because the transition animation gives us time to not notice the
64 * sudden rendering of the button. However, on web if we do this, there's an obvious flicker once the
65 * button renders. So, we update the state in both cases.
66 */
67 React.useEffect(() => {
68 const updateWasFollowing = () => {
69 if (wasFollowing !== isFollowing) {
70 setWasFollowing(isFollowing)
71 }
72 }
73
74 const unsubscribeFocus = navigation.addListener('focus', updateWasFollowing)
75 const unsubscribeBlur = navigation.addListener('blur', updateWasFollowing)
76
77 return () => {
78 unsubscribeFocus()
79 unsubscribeBlur()
80 }
81 }, [isFollowing, wasFollowing, navigation])
82
83 const onPress = React.useCallback(() => {
84 if (!isFollowing) {
85 requireAuth(async () => {
86 try {
87 await queueFollow()
88 } catch (e: any) {
89 if (e?.name !== 'AbortError') {
90 logger.error('Failed to follow', {message: String(e)})
91 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
92 }
93 }
94 })
95 } else {
96 requireAuth(async () => {
97 try {
98 await queueUnfollow()
99 } catch (e: any) {
100 if (e?.name !== 'AbortError') {
101 logger.error('Failed to unfollow', {message: String(e)})
102 Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
103 }
104 }
105 })
106 }
107 }, [isFollowing, requireAuth, queueFollow, _, queueUnfollow])
108
109 if (!showFollowBtn) return null
110
111 return (
112 <Button
113 testID="followBtn"
114 label={_(msg`Follow ${profile.handle}`)}
115 onPress={onPress}
116 size="small"
117 variant="solid"
118 color={isFollowing ? 'secondary' : 'secondary_inverted'}
119 style={[a.rounded_full]}>
120 {gtMobile && (
121 <ButtonIcon
122 icon={isFollowing ? Check : Plus}
123 position="left"
124 size="sm"
125 />
126 )}
127 <ButtonText>
128 {!isFollowing ? (
129 isFollowedBy ? (
130 <Trans>Follow Back</Trans>
131 ) : (
132 <Trans>Follow</Trans>
133 )
134 ) : (
135 <Trans>Following</Trans>
136 )}
137 </ButtonText>
138 </Button>
139 )
140}