mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {Pressable, ScrollView, StyleSheet, View} from 'react-native'
3import {AppBskyActorDefs, moderateProfile} from '@atproto/api'
4import {
5 FontAwesomeIcon,
6 FontAwesomeIconStyle,
7} from '@fortawesome/react-native-fontawesome'
8import {msg, Trans} from '@lingui/macro'
9import {useLingui} from '@lingui/react'
10
11import {useProfileShadow} from '#/state/cache/profile-shadow'
12import {useModerationOpts} from '#/state/preferences/moderation-opts'
13import {useProfileFollowMutationQueue} from '#/state/queries/profile'
14import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows'
15import {useAnalytics} from 'lib/analytics/analytics'
16import {usePalette} from 'lib/hooks/usePalette'
17import {makeProfileLink} from 'lib/routes/links'
18import {sanitizeDisplayName} from 'lib/strings/display-names'
19import {sanitizeHandle} from 'lib/strings/handles'
20import {isWeb} from 'platform/detection'
21import {Button} from 'view/com/util/forms/Button'
22import {Link} from 'view/com/util/Link'
23import {Text} from 'view/com/util/text/Text'
24import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
25import * as Toast from '../util/Toast'
26
27const OUTER_PADDING = 10
28const INNER_PADDING = 14
29const TOTAL_HEIGHT = 250
30
31export function ProfileHeaderSuggestedFollows({
32 actorDid,
33 requestDismiss,
34}: {
35 actorDid: string
36 requestDismiss: () => void
37}) {
38 const pal = usePalette('default')
39 const {isLoading, data} = useSuggestedFollowsByActorQuery({
40 did: actorDid,
41 })
42 return (
43 <View
44 style={{paddingVertical: OUTER_PADDING, height: TOTAL_HEIGHT}}
45 pointerEvents="box-none">
46 <View
47 pointerEvents="box-none"
48 style={{
49 backgroundColor: pal.viewLight.backgroundColor,
50 height: '100%',
51 paddingTop: INNER_PADDING / 2,
52 }}>
53 <View
54 pointerEvents="box-none"
55 style={{
56 flexDirection: 'row',
57 justifyContent: 'space-between',
58 alignItems: 'center',
59 paddingTop: 4,
60 paddingBottom: INNER_PADDING / 2,
61 paddingLeft: INNER_PADDING,
62 paddingRight: INNER_PADDING / 2,
63 }}>
64 <Text type="sm-bold" style={[pal.textLight]}>
65 <Trans>Suggested for you</Trans>
66 </Text>
67
68 <Pressable
69 accessibilityRole="button"
70 onPress={requestDismiss}
71 hitSlop={10}
72 style={{padding: INNER_PADDING / 2}}>
73 <FontAwesomeIcon
74 icon="x"
75 size={12}
76 style={pal.textLight as FontAwesomeIconStyle}
77 />
78 </Pressable>
79 </View>
80
81 <ScrollView
82 horizontal={true}
83 showsHorizontalScrollIndicator={isWeb}
84 persistentScrollbar={true}
85 scrollIndicatorInsets={{bottom: 0}}
86 scrollEnabled={true}
87 contentContainerStyle={{
88 alignItems: 'flex-start',
89 paddingLeft: INNER_PADDING / 2,
90 paddingBottom: INNER_PADDING,
91 }}>
92 {isLoading ? (
93 <>
94 <SuggestedFollowSkeleton />
95 <SuggestedFollowSkeleton />
96 <SuggestedFollowSkeleton />
97 <SuggestedFollowSkeleton />
98 <SuggestedFollowSkeleton />
99 <SuggestedFollowSkeleton />
100 </>
101 ) : data ? (
102 data.suggestions
103 .filter(s => (s.associated?.labeler ? false : true))
104 .map(profile => (
105 <SuggestedFollow key={profile.did} profile={profile} />
106 ))
107 ) : (
108 <View />
109 )}
110 </ScrollView>
111 </View>
112 </View>
113 )
114}
115
116function SuggestedFollowSkeleton() {
117 const pal = usePalette('default')
118 return (
119 <View
120 style={[
121 styles.suggestedFollowCardOuter,
122 {
123 backgroundColor: pal.view.backgroundColor,
124 },
125 ]}>
126 <View
127 style={{
128 height: 60,
129 width: 60,
130 borderRadius: 60,
131 backgroundColor: pal.viewLight.backgroundColor,
132 opacity: 0.6,
133 }}
134 />
135 <View
136 style={{
137 height: 17,
138 width: 70,
139 borderRadius: 4,
140 backgroundColor: pal.viewLight.backgroundColor,
141 marginTop: 12,
142 marginBottom: 4,
143 }}
144 />
145 <View
146 style={{
147 height: 12,
148 width: 70,
149 borderRadius: 4,
150 backgroundColor: pal.viewLight.backgroundColor,
151 marginBottom: 12,
152 opacity: 0.6,
153 }}
154 />
155 <View
156 style={{
157 height: 32,
158 borderRadius: 32,
159 width: '100%',
160 backgroundColor: pal.viewLight.backgroundColor,
161 }}
162 />
163 </View>
164 )
165}
166
167function SuggestedFollow({
168 profile: profileUnshadowed,
169}: {
170 profile: AppBskyActorDefs.ProfileView
171}) {
172 const {track} = useAnalytics()
173 const pal = usePalette('default')
174 const {_} = useLingui()
175 const moderationOpts = useModerationOpts()
176 const profile = useProfileShadow(profileUnshadowed)
177 const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
178 profile,
179 'ProfileHeaderSuggestedFollows',
180 )
181
182 const onPressFollow = React.useCallback(async () => {
183 try {
184 track('ProfileHeader:SuggestedFollowFollowed')
185 await queueFollow()
186 } catch (e: any) {
187 if (e?.name !== 'AbortError') {
188 Toast.show(_(msg`An issue occurred, please try again.`))
189 }
190 }
191 }, [queueFollow, track, _])
192
193 const onPressUnfollow = React.useCallback(async () => {
194 try {
195 await queueUnfollow()
196 } catch (e: any) {
197 if (e?.name !== 'AbortError') {
198 Toast.show(_(msg`An issue occurred, please try again.`))
199 }
200 }
201 }, [queueUnfollow, _])
202
203 if (!moderationOpts) {
204 return null
205 }
206 const moderation = moderateProfile(profile, moderationOpts)
207 const following = profile.viewer?.following
208 return (
209 <Link
210 href={makeProfileLink(profile)}
211 title={profile.handle}
212 asAnchor
213 anchorNoUnderline>
214 <View
215 style={[
216 styles.suggestedFollowCardOuter,
217 {
218 backgroundColor: pal.view.backgroundColor,
219 },
220 ]}>
221 <PreviewableUserAvatar
222 size={60}
223 profile={profile}
224 avatar={profile.avatar}
225 moderation={moderation.ui('avatar')}
226 />
227
228 <View style={{width: '100%', paddingVertical: 12}}>
229 <Text
230 type="xs-medium"
231 style={[pal.text, {textAlign: 'center'}]}
232 numberOfLines={1}>
233 {sanitizeDisplayName(
234 profile.displayName || sanitizeHandle(profile.handle),
235 moderation.ui('displayName'),
236 )}
237 </Text>
238 <Text
239 type="xs-medium"
240 style={[pal.textLight, {textAlign: 'center'}]}
241 numberOfLines={1}>
242 {sanitizeHandle(profile.handle, '@')}
243 </Text>
244 </View>
245
246 <Button
247 label={following ? _(msg`Unfollow`) : _(msg`Follow`)}
248 type="inverted"
249 labelStyle={{textAlign: 'center'}}
250 onPress={following ? onPressUnfollow : onPressFollow}
251 />
252 </View>
253 </Link>
254 )
255}
256
257const styles = StyleSheet.create({
258 suggestedFollowCardOuter: {
259 marginHorizontal: INNER_PADDING / 2,
260 paddingTop: 10,
261 paddingBottom: 12,
262 paddingHorizontal: 10,
263 borderRadius: 8,
264 width: 130,
265 alignItems: 'center',
266 overflow: 'hidden',
267 flexShrink: 1,
268 },
269})