mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {memo, useCallback} from 'react'
2import {type StyleProp, View, type ViewStyle} from 'react-native'
3import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api'
4import {msg} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6import {useQueryClient} from '@tanstack/react-query'
7import type React from 'react'
8
9import {useActorStatus} from '#/lib/actor-status'
10import {makeProfileLink} from '#/lib/routes/links'
11import {forceLTR} from '#/lib/strings/bidi'
12import {NON_BREAKING_SPACE} from '#/lib/strings/constants'
13import {sanitizeDisplayName} from '#/lib/strings/display-names'
14import {sanitizeHandle} from '#/lib/strings/handles'
15import {niceDate} from '#/lib/strings/time'
16import {isAndroid} from '#/platform/detection'
17import {useProfileShadow} from '#/state/cache/profile-shadow'
18import {precacheProfile} from '#/state/queries/profile'
19import {atoms as a, platform, useTheme, web} from '#/alf'
20import {WebOnlyInlineLinkText} from '#/components/Link'
21import {ProfileHoverCard} from '#/components/ProfileHoverCard'
22import {Text} from '#/components/Typography'
23import {useSimpleVerificationState} from '#/components/verification'
24import {VerificationCheck} from '#/components/verification/VerificationCheck'
25import {TimeElapsed} from './TimeElapsed'
26import {PreviewableUserAvatar} from './UserAvatar'
27
28interface PostMetaOpts {
29 author: AppBskyActorDefs.ProfileViewBasic
30 moderation: ModerationDecision | undefined
31 postHref: string
32 timestamp: string
33 showAvatar?: boolean
34 avatarSize?: number
35 onOpenAuthor?: () => void
36 style?: StyleProp<ViewStyle>
37}
38
39let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
40 const t = useTheme()
41 const {i18n, _} = useLingui()
42
43 const author = useProfileShadow(opts.author)
44 const displayName = author.displayName || author.handle
45 const handle = author.handle
46 const profileLink = makeProfileLink(author)
47 const queryClient = useQueryClient()
48 const onOpenAuthor = opts.onOpenAuthor
49 const onBeforePressAuthor = useCallback(() => {
50 precacheProfile(queryClient, author)
51 onOpenAuthor?.()
52 }, [queryClient, author, onOpenAuthor])
53 const onBeforePressPost = useCallback(() => {
54 precacheProfile(queryClient, author)
55 }, [queryClient, author])
56
57 const timestampLabel = niceDate(i18n, opts.timestamp)
58 const verification = useSimpleVerificationState({profile: author})
59 const {isActive: live} = useActorStatus(author)
60
61 return (
62 <View
63 style={[
64 a.flex_1,
65 a.flex_row,
66 a.align_center,
67 a.pb_xs,
68 a.gap_xs,
69 a.z_20,
70 opts.style,
71 ]}>
72 {opts.showAvatar && (
73 <View style={[a.self_center, a.mr_2xs]}>
74 <PreviewableUserAvatar
75 size={opts.avatarSize || 16}
76 profile={author}
77 moderation={opts.moderation?.ui('avatar')}
78 type={author.associated?.labeler ? 'labeler' : 'user'}
79 live={live}
80 hideLiveBadge
81 />
82 </View>
83 )}
84 <View style={[a.flex_row, a.align_end, a.flex_shrink]}>
85 <ProfileHoverCard did={author.did}>
86 <View style={[a.flex_row, a.align_end, a.flex_shrink]}>
87 <WebOnlyInlineLinkText
88 emoji
89 numberOfLines={1}
90 to={profileLink}
91 label={_(msg`View profile`)}
92 disableMismatchWarning
93 onPress={onBeforePressAuthor}
94 style={[
95 a.text_md,
96 a.font_bold,
97 t.atoms.text,
98 a.leading_tight,
99 {maxWidth: '70%', flexShrink: 0},
100 ]}>
101 {forceLTR(
102 sanitizeDisplayName(
103 displayName,
104 opts.moderation?.ui('displayName'),
105 ),
106 )}
107 </WebOnlyInlineLinkText>
108 {verification.showBadge && (
109 <View
110 style={[
111 a.pl_2xs,
112 a.self_center,
113 {
114 marginTop: platform({web: 0, ios: 0, android: -1}),
115 },
116 ]}>
117 <VerificationCheck
118 width={platform({android: 13, default: 12})}
119 verifier={verification.role === 'verifier'}
120 />
121 </View>
122 )}
123 <WebOnlyInlineLinkText
124 numberOfLines={1}
125 to={profileLink}
126 label={_(msg`View profile`)}
127 disableMismatchWarning
128 disableUnderline
129 onPress={onBeforePressAuthor}
130 style={[
131 a.text_md,
132 t.atoms.text_contrast_medium,
133 a.leading_tight,
134 {flexShrink: 10},
135 ]}>
136 {NON_BREAKING_SPACE + sanitizeHandle(handle, '@')}
137 </WebOnlyInlineLinkText>
138 </View>
139 </ProfileHoverCard>
140
141 <TimeElapsed timestamp={opts.timestamp}>
142 {({timeElapsed}) => (
143 <WebOnlyInlineLinkText
144 to={opts.postHref}
145 label={timestampLabel}
146 title={timestampLabel}
147 disableMismatchWarning
148 disableUnderline
149 onPress={onBeforePressPost}
150 style={[
151 a.pl_xs,
152 a.text_md,
153 a.leading_tight,
154 isAndroid && a.flex_grow,
155 a.text_right,
156 t.atoms.text_contrast_medium,
157 web({
158 whiteSpace: 'nowrap',
159 }),
160 ]}>
161 {!isAndroid && (
162 <Text
163 style={[
164 a.text_md,
165 a.leading_tight,
166 t.atoms.text_contrast_medium,
167 ]}
168 accessible={false}>
169 ·{' '}
170 </Text>
171 )}
172 {timeElapsed}
173 </WebOnlyInlineLinkText>
174 )}
175 </TimeElapsed>
176 </View>
177 </View>
178 )
179}
180PostMeta = memo(PostMeta)
181export {PostMeta}